summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/config/console.nix7
-rw-r--r--nixos/modules/config/fanout.nix49
-rw-r--r--nixos/modules/config/gnu.nix43
-rw-r--r--nixos/modules/config/iproute2.nix17
-rw-r--r--nixos/modules/config/mysql.nix4
-rw-r--r--nixos/modules/config/nix-channel.nix10
-rw-r--r--nixos/modules/config/qt.nix165
-rw-r--r--nixos/modules/config/stevenblack.nix4
-rw-r--r--nixos/modules/config/system-path.nix19
-rw-r--r--nixos/modules/config/terminfo.nix5
-rw-r--r--nixos/modules/config/users-groups.nix81
-rw-r--r--nixos/modules/hardware/all-firmware.nix26
-rw-r--r--nixos/modules/hardware/corectrl.nix4
-rw-r--r--nixos/modules/hardware/cpu/x86-msr.nix91
-rw-r--r--nixos/modules/hardware/device-tree.nix66
-rw-r--r--nixos/modules/hardware/i2c.nix2
-rw-r--r--nixos/modules/hardware/keyboard/uhk.nix2
-rw-r--r--nixos/modules/hardware/keyboard/zsa.nix2
-rw-r--r--nixos/modules/hardware/openrazer.nix2
-rw-r--r--nixos/modules/hardware/tuxedo-keyboard.nix2
-rw-r--r--nixos/modules/hardware/video/amdgpu-pro.nix3
-rw-r--r--nixos/modules/hardware/video/nvidia.nix32
-rw-r--r--nixos/modules/hardware/video/webcam/facetimehd.nix2
-rw-r--r--nixos/modules/image/repart.md137
-rw-r--r--nixos/modules/image/repart.nix58
-rw-r--r--nixos/modules/installer/cd-dvd/channel.nix54
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl32
-rw-r--r--nixos/modules/installer/tools/nixos-option/default.nix1
-rw-r--r--nixos/modules/installer/tools/tools.nix28
-rw-r--r--nixos/modules/installer/virtualbox-demo.nix2
-rw-r--r--nixos/modules/misc/ids.nix8
-rw-r--r--nixos/modules/misc/locate.nix37
-rw-r--r--nixos/modules/misc/nixops-autoluks.nix2
-rw-r--r--nixos/modules/misc/nixpkgs.nix34
-rw-r--r--nixos/modules/misc/version.nix42
-rw-r--r--nixos/modules/module-list.nix48
-rw-r--r--nixos/modules/profiles/image-based-appliance.nix26
-rw-r--r--nixos/modules/profiles/installation-device.nix2
-rw-r--r--nixos/modules/profiles/macos-builder.nix8
-rw-r--r--nixos/modules/profiles/minimal.nix9
-rw-r--r--nixos/modules/programs/ausweisapp.nix6
-rw-r--r--nixos/modules/programs/bandwhich.nix2
-rw-r--r--nixos/modules/programs/browserpass.nix2
-rw-r--r--nixos/modules/programs/calls.nix2
-rw-r--r--nixos/modules/programs/cdemu.nix13
-rw-r--r--nixos/modules/programs/cnping.nix2
-rw-r--r--nixos/modules/programs/direnv.nix4
-rw-r--r--nixos/modules/programs/environment.nix6
-rw-r--r--nixos/modules/programs/feedbackd.nix4
-rw-r--r--nixos/modules/programs/firefox.nix112
-rw-r--r--nixos/modules/programs/fish.nix6
-rw-r--r--nixos/modules/programs/gnupg.nix29
-rw-r--r--nixos/modules/programs/kdeconnect.nix2
-rw-r--r--nixos/modules/programs/nano.nix33
-rw-r--r--nixos/modules/programs/npm.nix2
-rw-r--r--nixos/modules/programs/openvpn3.nix20
-rw-r--r--nixos/modules/programs/projecteur.nix20
-rw-r--r--nixos/modules/programs/regreet.nix21
-rw-r--r--nixos/modules/programs/rust-motd.nix65
-rw-r--r--nixos/modules/programs/virt-manager.nix16
-rw-r--r--nixos/modules/programs/wayland/cardboard.nix24
-rw-r--r--nixos/modules/programs/wayland/wayfire.nix2
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.md2
-rw-r--r--nixos/modules/rename.nix2
-rw-r--r--nixos/modules/security/acme/default.nix65
-rw-r--r--nixos/modules/security/apparmor/profiles.nix6
-rw-r--r--nixos/modules/security/duosec.nix15
-rw-r--r--nixos/modules/security/google_oslogin.nix4
-rw-r--r--nixos/modules/security/pam.nix756
-rw-r--r--nixos/modules/security/pam_mount.nix6
-rw-r--r--nixos/modules/security/polkit.nix3
-rw-r--r--nixos/modules/security/sudo.nix153
-rw-r--r--nixos/modules/security/wrappers/default.nix80
-rw-r--r--nixos/modules/security/wrappers/wrapper.c57
-rw-r--r--nixos/modules/security/wrappers/wrapper.nix5
-rw-r--r--nixos/modules/services/audio/castopod.md22
-rw-r--r--nixos/modules/services/audio/castopod.nix287
-rw-r--r--nixos/modules/services/audio/gonic.nix1
-rw-r--r--nixos/modules/services/audio/jack.nix2
-rw-r--r--nixos/modules/services/audio/navidrome.nix7
-rw-r--r--nixos/modules/services/audio/slimserver.nix2
-rw-r--r--nixos/modules/services/audio/wyoming/faster-whisper.nix8
-rw-r--r--nixos/modules/services/audio/wyoming/openwakeword.nix163
-rw-r--r--nixos/modules/services/backup/bacula.nix40
-rw-r--r--nixos/modules/services/backup/borgbackup.nix11
-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/postgresql-wal-receiver.nix4
-rw-r--r--nixos/modules/services/backup/restic.nix54
-rw-r--r--nixos/modules/services/backup/syncoid.nix2
-rw-r--r--nixos/modules/services/backup/znapzend.nix20
-rw-r--r--nixos/modules/services/blockchain/ethereum/erigon.nix4
-rw-r--r--nixos/modules/services/cluster/hadoop/default.nix18
-rw-r--r--nixos/modules/services/cluster/hadoop/yarn.nix2
-rw-r--r--nixos/modules/services/computing/boinc/client.nix2
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix2
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix18
-rw-r--r--nixos/modules/services/continuous-integration/woodpecker/server.nix8
-rw-r--r--nixos/modules/services/databases/cassandra.nix2
-rw-r--r--nixos/modules/services/databases/couchdb.nix2
-rw-r--r--nixos/modules/services/databases/ferretdb.nix79
-rw-r--r--nixos/modules/services/databases/firebird.nix2
-rw-r--r--nixos/modules/services/databases/pgmanage.nix4
-rw-r--r--nixos/modules/services/databases/postgresql.md153
-rw-r--r--nixos/modules/services/databases/postgresql.nix95
-rw-r--r--nixos/modules/services/databases/redis.nix4
-rw-r--r--nixos/modules/services/databases/surrealdb.nix2
-rw-r--r--nixos/modules/services/desktops/deepin/app-services.nix2
-rw-r--r--nixos/modules/services/desktops/deepin/dde-api.nix4
-rw-r--r--nixos/modules/services/desktops/deepin/dde-daemon.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/at-spi2-core.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-browser-connector.nix6
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix17
-rw-r--r--nixos/modules/services/development/livebook.md39
-rw-r--r--nixos/modules/services/development/livebook.nix90
-rw-r--r--nixos/modules/services/development/zammad.nix4
-rw-r--r--nixos/modules/services/display-managers/greetd.nix1
-rw-r--r--nixos/modules/services/finance/odoo.nix2
-rw-r--r--nixos/modules/services/games/asf.nix22
-rw-r--r--nixos/modules/services/hardware/fwupd.nix20
-rw-r--r--nixos/modules/services/hardware/iptsd.nix53
-rw-r--r--nixos/modules/services/hardware/kanata.nix16
-rw-r--r--nixos/modules/services/hardware/keyd.nix32
-rw-r--r--nixos/modules/services/hardware/supergfxd.nix2
-rw-r--r--nixos/modules/services/hardware/throttled.nix6
-rw-r--r--nixos/modules/services/hardware/tlp.nix4
-rw-r--r--nixos/modules/services/hardware/tuxedo-rs.nix49
-rw-r--r--nixos/modules/services/hardware/udev.nix4
-rw-r--r--nixos/modules/services/hardware/undervolt.nix2
-rw-r--r--nixos/modules/services/home-automation/esphome.nix2
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix92
-rw-r--r--nixos/modules/services/home-automation/homeassistant-satellite.nix225
-rw-r--r--nixos/modules/services/home-automation/zwave-js.nix152
-rw-r--r--nixos/modules/services/logging/logstash.nix2
-rw-r--r--nixos/modules/services/logging/syslog-ng.nix2
-rw-r--r--nixos/modules/services/logging/ulogd.nix25
-rw-r--r--nixos/modules/services/mail/dovecot.nix2
-rw-r--r--nixos/modules/services/mail/listmonk.nix2
-rw-r--r--nixos/modules/services/mail/mailman.nix13
-rw-r--r--nixos/modules/services/mail/mlmmj.nix17
-rw-r--r--nixos/modules/services/mail/roundcube.nix14
-rw-r--r--nixos/modules/services/mail/sympa.nix10
-rw-r--r--nixos/modules/services/matrix/appservice-discord.nix4
-rw-r--r--nixos/modules/services/matrix/matrix-sliding-sync.nix17
-rw-r--r--nixos/modules/services/matrix/mautrix-facebook.nix6
-rw-r--r--nixos/modules/services/matrix/mautrix-telegram.nix4
-rw-r--r--nixos/modules/services/matrix/mautrix-whatsapp.nix4
-rw-r--r--nixos/modules/services/matrix/mjolnir.nix4
-rw-r--r--nixos/modules/services/matrix/mx-puppet-discord.nix4
-rw-r--r--nixos/modules/services/matrix/synapse.md2
-rw-r--r--nixos/modules/services/matrix/synapse.nix122
-rw-r--r--nixos/modules/services/misc/amazon-ssm-agent.nix (renamed from nixos/modules/services/misc/ssm-agent.nix)19
-rw-r--r--nixos/modules/services/misc/atuin.nix4
-rw-r--r--nixos/modules/services/misc/autofs.nix2
-rw-r--r--[-rwxr-xr-x]nixos/modules/services/misc/confd.nix0
-rw-r--r--nixos/modules/services/misc/dysnomia.nix2
-rw-r--r--nixos/modules/services/misc/forgejo.md79
-rw-r--r--nixos/modules/services/misc/forgejo.nix13
-rw-r--r--nixos/modules/services/misc/gitea.nix25
-rw-r--r--nixos/modules/services/misc/gollum.nix2
-rw-r--r--nixos/modules/services/misc/gpsd.nix12
-rw-r--r--nixos/modules/services/misc/klipper.nix4
-rw-r--r--nixos/modules/services/misc/mediatomb.nix2
-rw-r--r--nixos/modules/services/misc/moonraker.nix10
-rw-r--r--nixos/modules/services/misc/packagekit.nix4
-rw-r--r--nixos/modules/services/misc/paperless.nix53
-rw-r--r--nixos/modules/services/misc/redmine.nix4
-rw-r--r--nixos/modules/services/misc/rkvm.nix164
-rw-r--r--nixos/modules/services/misc/rshim.nix2
-rw-r--r--nixos/modules/services/misc/soft-serve.nix99
-rw-r--r--nixos/modules/services/misc/sourcehut/default.nix160
-rw-r--r--nixos/modules/services/misc/sourcehut/service.nix40
-rw-r--r--nixos/modules/services/misc/spice-autorandr.nix26
-rw-r--r--nixos/modules/services/misc/tp-auto-kbbl.nix2
-rw-r--r--nixos/modules/services/misc/xmr-stak.nix8
-rw-r--r--nixos/modules/services/misc/xmrig.nix6
-rw-r--r--nixos/modules/services/misc/zoneminder.nix4
-rw-r--r--nixos/modules/services/monitoring/bosun.nix2
-rw-r--r--nixos/modules/services/monitoring/certspotter.md74
-rw-r--r--nixos/modules/services/monitoring/certspotter.nix143
-rw-r--r--nixos/modules/services/monitoring/goss.md44
-rw-r--r--nixos/modules/services/monitoring/goss.nix86
-rw-r--r--nixos/modules/services/monitoring/grafana-image-renderer.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana.nix5
-rw-r--r--nixos/modules/services/monitoring/graphite.nix2
-rw-r--r--nixos/modules/services/monitoring/librenms.nix624
-rw-r--r--nixos/modules/services/monitoring/mackerel-agent.nix6
-rw-r--r--nixos/modules/services/monitoring/munin.nix51
-rw-r--r--nixos/modules/services/monitoring/nagios.nix2
-rw-r--r--nixos/modules/services/monitoring/netdata.nix12
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.nix1
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix91
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix55
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/imap-mailstat.nix71
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/kea.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/knot.nix19
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix145
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix2
-rw-r--r--nixos/modules/services/monitoring/smartd.nix2
-rw-r--r--nixos/modules/services/monitoring/ups.nix8
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix4
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix4
-rw-r--r--nixos/modules/services/network-filesystems/kubo.nix2
-rw-r--r--nixos/modules/services/network-filesystems/openafs/server.nix4
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/server.nix2
-rw-r--r--nixos/modules/services/network-filesystems/samba.nix2
-rw-r--r--nixos/modules/services/network-filesystems/xtreemfs.nix10
-rw-r--r--nixos/modules/services/networking/asterisk.nix2
-rw-r--r--nixos/modules/services/networking/bitcoind.nix3
-rw-r--r--nixos/modules/services/networking/connman.nix69
-rw-r--r--nixos/modules/services/networking/coredns.nix9
-rw-r--r--nixos/modules/services/networking/create_ap.nix2
-rw-r--r--nixos/modules/services/networking/dae.nix63
-rw-r--r--nixos/modules/services/networking/ddclient.nix234
-rw-r--r--nixos/modules/services/networking/deconz.nix125
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix4
-rw-r--r--nixos/modules/services/networking/fastnetmon-advanced.nix222
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.md2
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.nix8
-rw-r--r--nixos/modules/services/networking/go-neb.nix2
-rw-r--r--nixos/modules/services/networking/gvpe.nix2
-rw-r--r--nixos/modules/services/networking/harmonia.nix2
-rw-r--r--nixos/modules/services/networking/hostapd.nix34
-rw-r--r--nixos/modules/services/networking/hylafax/options.nix8
-rw-r--r--nixos/modules/services/networking/i2pd.nix10
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/builder.sh2
-rw-r--r--nixos/modules/services/networking/iscsi/initiator.nix38
-rw-r--r--nixos/modules/services/networking/kea.nix21
-rw-r--r--nixos/modules/services/networking/knot.nix16
-rw-r--r--nixos/modules/services/networking/mullvad-vpn.nix6
-rw-r--r--nixos/modules/services/networking/multipath.nix3
-rw-r--r--nixos/modules/services/networking/nar-serve.nix2
-rw-r--r--nixos/modules/services/networking/netclient.nix27
-rw-r--r--nixos/modules/services/networking/networkmanager.nix93
-rw-r--r--nixos/modules/services/networking/nftables.nix2
-rw-r--r--nixos/modules/services/networking/nix-serve.nix2
-rw-r--r--nixos/modules/services/networking/ntp/chrony.nix39
-rw-r--r--nixos/modules/services/networking/ntp/ntpd.nix2
-rw-r--r--nixos/modules/services/networking/pleroma.nix2
-rw-r--r--nixos/modules/services/networking/prayer.nix90
-rw-r--r--nixos/modules/services/networking/prosody.nix3
-rw-r--r--nixos/modules/services/networking/rosenpass.nix233
-rw-r--r--nixos/modules/services/networking/searx.nix33
-rw-r--r--nixos/modules/services/networking/snowflake-proxy.nix2
-rw-r--r--nixos/modules/services/networking/spiped.nix5
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix123
-rw-r--r--nixos/modules/services/networking/sslh.nix189
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/module.nix30
-rw-r--r--nixos/modules/services/networking/syncthing.nix64
-rw-r--r--nixos/modules/services/networking/tailscale.nix8
-rw-r--r--nixos/modules/services/networking/tinyproxy.nix103
-rw-r--r--nixos/modules/services/networking/tox-bootstrapd.nix2
-rw-r--r--nixos/modules/services/networking/trust-dns.nix4
-rw-r--r--nixos/modules/services/networking/unbound.nix28
-rw-r--r--nixos/modules/services/networking/unifi.nix80
-rw-r--r--nixos/modules/services/networking/wireguard.nix12
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix2
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix13
-rw-r--r--nixos/modules/services/printing/cupsd.nix22
-rw-r--r--nixos/modules/services/search/kibana.nix213
-rw-r--r--nixos/modules/services/search/opensearch.nix19
-rw-r--r--nixos/modules/services/security/fail2ban.nix8
-rw-r--r--nixos/modules/services/security/hockeypuck.nix2
-rw-r--r--nixos/modules/services/security/jitterentropy-rngd.nix18
-rw-r--r--nixos/modules/services/security/opensnitch.nix2
-rw-r--r--nixos/modules/services/security/privacyidea.nix458
-rw-r--r--nixos/modules/services/security/tang.nix95
-rw-r--r--nixos/modules/services/security/usbguard.nix21
-rw-r--r--nixos/modules/services/system/earlyoom.nix2
-rw-r--r--nixos/modules/services/system/nix-daemon.nix5
-rw-r--r--nixos/modules/services/system/systembus-notify.nix2
-rw-r--r--nixos/modules/services/torrent/flexget.nix3
-rw-r--r--nixos/modules/services/torrent/transmission.nix4
-rw-r--r--nixos/modules/services/video/mediamtx.nix2
-rw-r--r--nixos/modules/services/video/unifi-video.nix4
-rw-r--r--nixos/modules/services/web-apps/akkoma.nix25
-rw-r--r--nixos/modules/services/web-apps/audiobookshelf.nix90
-rw-r--r--nixos/modules/services/web-apps/c2fmzq-server.md42
-rw-r--r--nixos/modules/services/web-apps/c2fmzq-server.nix125
-rw-r--r--nixos/modules/services/web-apps/cloudlog.nix2
-rw-r--r--nixos/modules/services/web-apps/code-server.nix259
-rw-r--r--nixos/modules/services/web-apps/coder.nix10
-rw-r--r--nixos/modules/services/web-apps/dex.nix7
-rw-r--r--nixos/modules/services/web-apps/discourse.nix4
-rw-r--r--nixos/modules/services/web-apps/freshrss.nix6
-rw-r--r--nixos/modules/services/web-apps/gotosocial.nix4
-rw-r--r--nixos/modules/services/web-apps/grocy.nix4
-rw-r--r--nixos/modules/services/web-apps/hedgedoc.nix1226
-rw-r--r--nixos/modules/services/web-apps/hledger-web.nix2
-rw-r--r--nixos/modules/services/web-apps/honk.nix2
-rw-r--r--nixos/modules/services/web-apps/invidious.nix15
-rw-r--r--nixos/modules/services/web-apps/isso.nix4
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix182
-rw-r--r--nixos/modules/services/web-apps/lanraragi.nix100
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix2
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix138
-rw-r--r--nixos/modules/services/web-apps/mattermost.nix6
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix96
-rw-r--r--nixos/modules/services/web-apps/meme-bingo-web.nix4
-rw-r--r--nixos/modules/services/web-apps/microbin.nix93
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix17
-rw-r--r--nixos/modules/services/web-apps/mobilizon.nix17
-rw-r--r--nixos/modules/services/web-apps/moodle.nix4
-rw-r--r--nixos/modules/services/web-apps/netbox.nix17
-rw-r--r--nixos/modules/services/web-apps/nextcloud.md10
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix124
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix2
-rw-r--r--nixos/modules/services/web-apps/outline.nix52
-rw-r--r--nixos/modules/services/web-apps/peering-manager.nix238
-rw-r--r--nixos/modules/services/web-apps/peertube.nix131
-rw-r--r--nixos/modules/services/web-apps/phylactery.nix2
-rw-r--r--nixos/modules/services/web-apps/pixelfed.nix1
-rw-r--r--nixos/modules/services/web-apps/plantuml-server.nix118
-rw-r--r--nixos/modules/services/web-apps/plausible.nix60
-rw-r--r--nixos/modules/services/web-apps/rimgo.nix107
-rw-r--r--nixos/modules/services/web-apps/shiori.nix9
-rw-r--r--nixos/modules/services/web-apps/snipe-it.nix18
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix15
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix2
-rw-r--r--nixos/modules/services/web-apps/writefreely.nix2
-rw-r--r--nixos/modules/services/web-apps/zitadel.nix223
-rw-r--r--nixos/modules/services/web-servers/garage.nix16
-rw-r--r--nixos/modules/services/web-servers/hydron.nix2
-rw-r--r--nixos/modules/services/web-servers/jboss/builder.sh2
-rw-r--r--nixos/modules/services/web-servers/keter/default.nix2
-rw-r--r--nixos/modules/services/web-servers/lighttpd/default.nix7
-rw-r--r--nixos/modules/services/web-servers/mighttpd2.nix4
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix42
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix12
-rw-r--r--nixos/modules/services/web-servers/rustus.nix2
-rw-r--r--nixos/modules/services/web-servers/stargazer.nix8
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/deepin.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix24
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix8
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix10
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix29
-rw-r--r--nixos/modules/services/x11/display-managers/xpra.nix6
-rw-r--r--nixos/modules/services/x11/extra-layouts.nix46
-rw-r--r--nixos/modules/services/x11/imwheel.nix2
-rw-r--r--nixos/modules/services/x11/xserver.nix119
-rw-r--r--nixos/modules/system/activation/activatable-system.nix65
-rw-r--r--nixos/modules/system/activation/activation-script.nix28
-rw-r--r--nixos/modules/system/activation/bootspec.nix2
-rwxr-xr-xnixos/modules/system/activation/switch-to-configuration.pl8
-rw-r--r--nixos/modules/system/activation/switchable-system.nix55
-rw-r--r--nixos/modules/system/boot/binfmt.nix51
-rw-r--r--nixos/modules/system/boot/grow-partition.nix56
-rw-r--r--nixos/modules/system/boot/initrd-network.nix16
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix11
-rw-r--r--nixos/modules/system/boot/kernel.nix12
-rw-r--r--nixos/modules/system/boot/loader/external/external.nix2
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix2
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix2
-rw-r--r--[-rwxr-xr-x]nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py221
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix10
-rw-r--r--nixos/modules/system/boot/luksroot.nix2
-rw-r--r--nixos/modules/system/boot/networkd.nix175
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh5
-rw-r--r--nixos/modules/system/boot/stage-1.nix17
-rwxr-xr-xnixos/modules/system/boot/stage-2-init.sh2
-rw-r--r--nixos/modules/system/boot/systemd.nix3
-rw-r--r--nixos/modules/system/boot/systemd/homed.nix2
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix40
-rw-r--r--nixos/modules/system/boot/systemd/journald.nix11
-rw-r--r--nixos/modules/system/boot/systemd/repart.nix9
-rw-r--r--nixos/modules/system/boot/systemd/tmpfiles.nix104
-rw-r--r--nixos/modules/system/boot/systemd/userdbd.nix2
-rw-r--r--nixos/modules/system/boot/timesyncd.nix45
-rw-r--r--nixos/modules/tasks/encrypted-devices.nix69
-rw-r--r--nixos/modules/tasks/filesystems.nix9
-rw-r--r--nixos/modules/tasks/filesystems/bcachefs.nix142
-rw-r--r--nixos/modules/tasks/filesystems/btrfs.nix17
-rw-r--r--nixos/modules/tasks/filesystems/cifs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/ext.nix2
-rw-r--r--nixos/modules/tasks/filesystems/f2fs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/jfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/reiserfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/vfat.nix2
-rw-r--r--nixos/modules/tasks/filesystems/xfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix22
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix64
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix76
-rw-r--r--nixos/modules/tasks/network-interfaces.nix89
-rw-r--r--nixos/modules/tasks/swraid.nix4
-rw-r--r--nixos/modules/testing/test-instrumentation.nix153
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix18
-rw-r--r--nixos/modules/virtualisation/azure-image.nix46
-rw-r--r--nixos/modules/virtualisation/google-compute-config.nix4
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix13
-rw-r--r--nixos/modules/virtualisation/incus.nix236
-rw-r--r--nixos/modules/virtualisation/lxc-container.nix41
-rw-r--r--nixos/modules/virtualisation/lxd.nix4
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix15
-rw-r--r--nixos/modules/virtualisation/oci-common.nix2
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix59
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix85
-rw-r--r--nixos/modules/virtualisation/vagrant-guest.nix1
-rw-r--r--nixos/modules/virtualisation/virtualbox-host.nix2
406 files changed, 10872 insertions, 5340 deletions
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index 1e8bb78f302..0a931c6918f 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -127,8 +127,8 @@ in
               ${optionalString (config.environment.sessionVariables ? XKB_CONFIG_ROOT)
                 "-I${config.environment.sessionVariables.XKB_CONFIG_ROOT}"
               } \
-              -model '${xkbModel}' -layout '${layout}' \
-              -option '${xkbOptions}' -variant '${xkbVariant}' > "$out"
+              -model '${xkb.model}' -layout '${xkb.layout}' \
+              -option '${xkb.options}' -variant '${xkb.variant}' > "$out"
           '');
     }
 
@@ -168,6 +168,9 @@ in
           # ...but only the keymaps if we don't
           "/etc/kbd/keymaps" = lib.mkIf (!cfg.earlySetup) { source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share/keymaps"; };
         };
+        boot.initrd.systemd.additionalUpstreamUnits = [
+          "systemd-vconsole-setup.service"
+        ];
         boot.initrd.systemd.storePaths = [
           "${config.boot.initrd.systemd.package}/lib/systemd/systemd-vconsole-setup"
           "${config.boot.initrd.systemd.package.kbd}/bin/setfont"
diff --git a/nixos/modules/config/fanout.nix b/nixos/modules/config/fanout.nix
new file mode 100644
index 00000000000..60ee145f19a
--- /dev/null
+++ b/nixos/modules/config/fanout.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.fanout;
+  mknodCmds = n: lib.lists.imap0 (i: s:
+    "mknod /dev/fanout${builtins.toString i} c $MAJOR ${builtins.toString i}"
+  ) (lib.lists.replicate n "");
+in
+{
+  options.services.fanout = {
+    enable = lib.mkEnableOption (lib.mdDoc "fanout");
+    fanoutDevices = lib.mkOption {
+      type = lib.types.int;
+      default = 1;
+      description = "Number of /dev/fanout devices";
+    };
+    bufferSize = lib.mkOption {
+      type = lib.types.int;
+      default = 16384;
+      description = "Size of /dev/fanout buffer in bytes";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    boot.extraModulePackages = [ config.boot.kernelPackages.fanout.out ];
+
+    boot.kernelModules = [ "fanout" ];
+
+    boot.extraModprobeConfig = ''
+      options fanout buffersize=${builtins.toString cfg.bufferSize}
+    '';
+
+    systemd.services.fanout = {
+      description = "Bring up /dev/fanout devices";
+      script = ''
+        MAJOR=$(${pkgs.gnugrep}/bin/grep fanout /proc/devices | ${pkgs.gawk}/bin/awk '{print $1}')
+        ${lib.strings.concatLines (mknodCmds cfg.fanoutDevices)}
+      '';
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "root";
+        RemainAfterExit = "yes";
+        Restart = "no";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/config/gnu.nix b/nixos/modules/config/gnu.nix
deleted file mode 100644
index a47d299b226..00000000000
--- a/nixos/modules/config/gnu.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-{
-  options = {
-    gnu = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        When enabled, GNU software is chosen by default whenever a there is
-        a choice between GNU and non-GNU software (e.g., GNU lsh
-        vs. OpenSSH).
-      '';
-    };
-  };
-
-  config = lib.mkIf config.gnu {
-
-    environment.systemPackages = with pkgs;
-      # TODO: Adjust `requiredPackages' from `system-path.nix'.
-      # TODO: Add Inetutils once it has the new `ifconfig'.
-      [ parted
-        #fdisk  # XXX: GNU fdisk currently fails to build and it's redundant
-                # with the `parted' command.
-        nano zile
-        texinfo # for the stand-alone Info reader
-      ]
-      ++ lib.optional (!stdenv.isAarch32) grub2;
-
-
-    # GNU GRUB, where available.
-    boot.loader.grub.enable = !pkgs.stdenv.isAarch32;
-
-    # GNU lsh.
-    services.openssh.enable = false;
-    services.lshd.enable = true;
-    programs.ssh.startAgent = false;
-    services.xserver.startGnuPGAgent = true;
-
-    # TODO: GNU dico.
-    # TODO: GNU Inetutils' inetd.
-    # TODO: GNU Pies.
-  };
-}
diff --git a/nixos/modules/config/iproute2.nix b/nixos/modules/config/iproute2.nix
index 8f49e7dbf7d..78bd07d680e 100644
--- a/nixos/modules/config/iproute2.nix
+++ b/nixos/modules/config/iproute2.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.networking.iproute2 = {
-    enable = mkEnableOption (lib.mdDoc "copy IP route configuration files");
+    enable = mkEnableOption (lib.mdDoc "copying IP route configuration files");
     rttablesExtraConfig = mkOption {
       type = types.lines;
       default = "";
@@ -18,15 +18,10 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.etc."iproute2/bpf_pinning" = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/bpf_pinning"; };
-    environment.etc."iproute2/ematch_map"  = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/ematch_map";  };
-    environment.etc."iproute2/group"       = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/group";       };
-    environment.etc."iproute2/nl_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/nl_protos";   };
-    environment.etc."iproute2/rt_dsfield"  = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_dsfield";  };
-    environment.etc."iproute2/rt_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_protos";   };
-    environment.etc."iproute2/rt_realms"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_realms";   };
-    environment.etc."iproute2/rt_scopes"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_scopes";   };
-    environment.etc."iproute2/rt_tables"   = { mode = "0644"; text = (fileContents "${pkgs.iproute2}/etc/iproute2/rt_tables")
-                                                                   + (optionalString (cfg.rttablesExtraConfig != "") "\n\n${cfg.rttablesExtraConfig}"); };
+    environment.etc."iproute2/rt_tables" = {
+      mode = "0644";
+      text = (fileContents "${pkgs.iproute2}/lib/iproute2/rt_tables")
+        + (optionalString (cfg.rttablesExtraConfig != "") "\n\n${cfg.rttablesExtraConfig}");
+    };
   };
 }
diff --git a/nixos/modules/config/mysql.nix b/nixos/modules/config/mysql.nix
index 2f13c56f2ae..95c9ba76663 100644
--- a/nixos/modules/config/mysql.nix
+++ b/nixos/modules/config/mysql.nix
@@ -429,11 +429,11 @@ in
       '';
     };
 
-    # Activation script to append the password from the password file
+    # preStart script to append the password from the password file
     # to the configuration files. It also fixes the owner of the
     # libnss-mysql-root.cfg because it is changed to root after the
     # password is appended.
-    system.activationScripts.mysql-auth-passwords = ''
+    systemd.services.mysql.preStart = ''
       if [[ -r ${cfg.passwordFile} ]]; then
         org_umask=$(umask)
         umask 0077
diff --git a/nixos/modules/config/nix-channel.nix b/nixos/modules/config/nix-channel.nix
index 3f8e088ede9..a7ca7a5c74a 100644
--- a/nixos/modules/config/nix-channel.nix
+++ b/nixos/modules/config/nix-channel.nix
@@ -97,12 +97,8 @@ in
 
     nix.settings.nix-path = mkIf (! cfg.channel.enable) (mkDefault "");
 
-    system.activationScripts.nix-channel = mkIf cfg.channel.enable
-      (stringAfter [ "etc" "users" ] ''
-        # Subscribe the root user to the NixOS channel by default.
-        if [ ! -e "/root/.nix-channels" ]; then
-            echo "${config.system.defaultChannel} nixos" > "/root/.nix-channels"
-        fi
-      '');
+    systemd.tmpfiles.rules = lib.mkIf cfg.channel.enable [
+      ''f /root/.nix-channels - - - - ${config.system.defaultChannel} nixos\n''
+    ];
   };
 }
diff --git a/nixos/modules/config/qt.nix b/nixos/modules/config/qt.nix
index 2b09281e467..f82b7ab85a8 100644
--- a/nixos/modules/config/qt.nix
+++ b/nixos/modules/config/qt.nix
@@ -1,121 +1,154 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   cfg = config.qt;
 
-  isQGnome = cfg.platformTheme == "gnome" && builtins.elem cfg.style ["adwaita" "adwaita-dark"];
-  isQtStyle = cfg.platformTheme == "gtk2" && !(builtins.elem cfg.style ["adwaita" "adwaita-dark"]);
-  isQt5ct = cfg.platformTheme == "qt5ct";
-  isLxqt = cfg.platformTheme == "lxqt";
-  isKde = cfg.platformTheme == "kde";
-
-  packages =
-    if isQGnome then [
-      pkgs.qgnomeplatform
-      pkgs.adwaita-qt
-      pkgs.qgnomeplatform-qt6
-      pkgs.adwaita-qt6
-    ]
-    else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins pkgs.qt6Packages.qt6gtk2 ]
-    else if isQt5ct then [ pkgs.libsForQt5.qt5ct pkgs.qt6Packages.qt6ct ]
-    else if isLxqt then [ pkgs.lxqt.lxqt-qtplugin pkgs.lxqt.lxqt-config ]
-    else if isKde then [ pkgs.libsForQt5.plasma-integration pkgs.libsForQt5.systemsettings ]
-    else throw "`qt.platformTheme` ${cfg.platformTheme} and `qt.style` ${cfg.style} are not compatible.";
+  platformPackages = with pkgs; {
+    gnome = [ qgnomeplatform qgnomeplatform-qt6 ];
+    gtk2 = [ libsForQt5.qtstyleplugins qt6Packages.qt6gtk2 ];
+    kde = [ libsForQt5.plasma-integration libsForQt5.systemsettings ];
+    lxqt = [ lxqt.lxqt-qtplugin lxqt.lxqt-config ];
+    qt5ct = [ libsForQt5.qt5ct qt6Packages.qt6ct ];
+  };
+
+  stylePackages = with pkgs; {
+    bb10bright = [ libsForQt5.qtstyleplugins ];
+    bb10dark = [ libsForQt5.qtstyleplugins ];
+    cde = [ libsForQt5.qtstyleplugins ];
+    cleanlooks = [ libsForQt5.qtstyleplugins ];
+    gtk2 = [ libsForQt5.qtstyleplugins qt6Packages.qt6gtk2 ];
+    motif = [ libsForQt5.qtstyleplugins ];
+    plastique = [ libsForQt5.qtstyleplugins ];
 
-in
+    adwaita = [ adwaita-qt adwaita-qt6 ];
+    adwaita-dark = [ adwaita-qt adwaita-qt6 ];
+    adwaita-highcontrast = [ adwaita-qt adwaita-qt6 ];
+    adwaita-highcontrastinverse = [ adwaita-qt adwaita-qt6 ];
+
+    breeze = [ libsForQt5.breeze-qt5 ];
 
+    kvantum = [ libsForQt5.qtstyleplugin-kvantum qt6Packages.qtstyleplugin-kvantum ];
+  };
+in
 {
-  meta.maintainers = [ maintainers.romildo ];
+  meta.maintainers = with lib.maintainers; [ romildo thiagokokada ];
 
   imports = [
-    (mkRenamedOptionModule ["qt5" "enable" ] ["qt" "enable" ])
-    (mkRenamedOptionModule ["qt5" "platformTheme" ] ["qt" "platformTheme" ])
-    (mkRenamedOptionModule ["qt5" "style" ] ["qt" "style" ])
+    (lib.mkRenamedOptionModule [ "qt5" "enable" ] [ "qt" "enable" ])
+    (lib.mkRenamedOptionModule [ "qt5" "platformTheme" ] [ "qt" "platformTheme" ])
+    (lib.mkRenamedOptionModule [ "qt5" "style" ] [ "qt" "style" ])
   ];
 
   options = {
     qt = {
+      enable = lib.mkEnableOption "" // {
+        description = lib.mdDoc ''
+          Whether to enable Qt configuration, including theming.
 
-      enable = mkEnableOption (lib.mdDoc "Qt theming configuration");
+          Enabling this option is necessary for Qt plugins to work in the
+          installed profiles (e.g.: `nix-env -i` or `environment.systemPackages`).
+        '';
+      };
 
-      platformTheme = mkOption {
-        type = types.enum [
-          "gtk2"
-          "gnome"
-          "lxqt"
-          "qt5ct"
-          "kde"
-        ];
+      platformTheme = lib.mkOption {
+        type = with lib.types; nullOr (enum (lib.attrNames platformPackages));
+        default = null;
         example = "gnome";
         relatedPackages = [
           "qgnomeplatform"
           "qgnomeplatform-qt6"
-          ["libsForQt5" "qtstyleplugins"]
-          ["libsForQt5" "qt5ct"]
-          ["lxqt" "lxqt-qtplugin"]
-          ["libsForQt5" "plasma-integration"]
+          [ "libsForQt5" "plasma-integration" ]
+          [ "libsForQt5" "qt5ct" ]
+          [ "libsForQt5" "qtstyleplugins" ]
+          [ "libsForQt5" "systemsettings" ]
+          [ "lxqt" "lxqt-config" ]
+          [ "lxqt" "lxqt-qtplugin" ]
+          [ "qt6Packages" "qt6ct" ]
+          [ "qt6Packages" "qt6gtk2" ]
         ];
         description = lib.mdDoc ''
           Selects the platform theme to use for Qt applications.
 
           The options are
-          - `gtk`: Use GTK theme with [qtstyleplugins](https://github.com/qt/qtstyleplugins)
           - `gnome`: Use GNOME theme with [qgnomeplatform](https://github.com/FedoraQt/QGnomePlatform)
+          - `gtk2`: Use GTK theme with [qtstyleplugins](https://github.com/qt/qtstyleplugins)
+          - `kde`: Use Qt settings from Plasma.
           - `lxqt`: Use LXQt style set using the [lxqt-config-appearance](https://github.com/lxqt/lxqt-config)
              application.
           - `qt5ct`: Use Qt style set using the [qt5ct](https://sourceforge.net/projects/qt5ct/)
-             application.
-          - `kde`: Use Qt settings from Plasma.
+             and [qt6ct](https://github.com/trialuser02/qt6ct) applications.
         '';
       };
 
-      style = mkOption {
-        type = types.enum [
-          "adwaita"
-          "adwaita-dark"
-          "cleanlooks"
-          "gtk2"
-          "motif"
-          "plastique"
-        ];
+      style = lib.mkOption {
+        type = with lib.types; nullOr (enum (lib.attrNames stylePackages));
+        default = null;
         example = "adwaita";
         relatedPackages = [
           "adwaita-qt"
           "adwaita-qt6"
-          ["libsForQt5" "qtstyleplugins"]
-          ["qt6Packages" "qt6gtk2"]
+          [ "libsForQt5" "breeze-qt5" ]
+          [ "libsForQt5" "qtstyleplugin-kvantum" ]
+          [ "libsForQt5" "qtstyleplugins" ]
+          [ "qt6Packages" "qt6gtk2" ]
+          [ "qt6Packages" "qtstyleplugin-kvantum" ]
         ];
         description = lib.mdDoc ''
           Selects the style to use for Qt applications.
 
           The options are
-          - `adwaita`, `adwaita-dark`: Use Adwaita Qt style with
+          - `adwaita`, `adwaita-dark`, `adwaita-highcontrast`, `adawaita-highcontrastinverse`:
+            Use Adwaita Qt style with
             [adwaita](https://github.com/FedoraQt/adwaita-qt)
-          - `cleanlooks`, `gtk2`, `motif`, `plastique`: Use styles from
+          - `breeze`: Use the Breeze style from
+            [breeze](https://github.com/KDE/breeze)
+          - `bb10bright`, `bb10dark`, `cleanlooks`, `gtk2`, `motif`, `plastique`:
+            Use styles from
             [qtstyleplugins](https://github.com/qt/qtstyleplugins)
+          - `kvantum`: Use styles from
+            [kvantum](https://github.com/tsujan/Kvantum)
         '';
       };
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
+    assertions =
+      let
+        gnomeStyles = [
+          "adwaita"
+          "adwaita-dark"
+          "adwaita-highcontrast"
+          "adwaita-highcontrastinverse"
+          "breeze"
+        ];
+      in
+      [
+        {
+          assertion = cfg.platformTheme == "gnome" -> (builtins.elem cfg.style gnomeStyles);
+          message = ''
+            `qt.platformTheme` "gnome" must have `qt.style` set to a theme that supports both Qt and Gtk,
+            for example: ${lib.concatStringsSep ", " gnomeStyles}.
+          '';
+        }
+      ];
 
     environment.variables = {
-      QT_QPA_PLATFORMTHEME = cfg.platformTheme;
-      QT_STYLE_OVERRIDE = mkIf (! (isQt5ct || isLxqt || isKde)) cfg.style;
+      QT_QPA_PLATFORMTHEME = lib.mkIf (cfg.platformTheme != null) cfg.platformTheme;
+      QT_STYLE_OVERRIDE = lib.mkIf (cfg.style != null) cfg.style;
     };
 
-    environment.profileRelativeSessionVariables = let
-      qtVersions = with pkgs; [ qt5 qt6 ];
-    in {
-      QT_PLUGIN_PATH = map (qt: "/${qt.qtbase.qtPluginPrefix}") qtVersions;
-      QML2_IMPORT_PATH = map (qt: "/${qt.qtbase.qtQmlPrefix}") qtVersions;
-    };
-
-    environment.systemPackages = packages;
+    environment.profileRelativeSessionVariables =
+      let
+        qtVersions = with pkgs; [ qt5 qt6 ];
+      in
+      {
+        QT_PLUGIN_PATH = map (qt: "/${qt.qtbase.qtPluginPrefix}") qtVersions;
+        QML2_IMPORT_PATH = map (qt: "/${qt.qtbase.qtQmlPrefix}") qtVersions;
+      };
 
+    environment.systemPackages =
+      lib.optionals (cfg.platformTheme != null) (platformPackages.${cfg.platformTheme})
+      ++ lib.optionals (cfg.style != null) (stylePackages.${cfg.style});
   };
 }
diff --git a/nixos/modules/config/stevenblack.nix b/nixos/modules/config/stevenblack.nix
index 07a0aa339a5..7e623516984 100644
--- a/nixos/modules/config/stevenblack.nix
+++ b/nixos/modules/config/stevenblack.nix
@@ -15,7 +15,7 @@ let
 in
 {
   options.networking.stevenblack = {
-    enable = mkEnableOption (mdDoc "Enable the stevenblack hosts file blocklist");
+    enable = mkEnableOption (mdDoc "the stevenblack hosts file blocklist");
 
     block = mkOption {
       type = types.listOf (types.enum [ "fakenews" "gambling" "porn" "social" ]);
@@ -30,5 +30,5 @@ in
       ++ optionals (activatedHosts == [ ]) [ "${pkgs.stevenblack-blocklist}/hosts" ];
   };
 
-  meta.maintainers = [ maintainers.fortuneteller2k maintainers.artturin ];
+  meta.maintainers = [ maintainers.moni maintainers.artturin ];
 }
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index e8bbeac4f72..71274ea8999 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -42,8 +42,7 @@ let
     ];
 
   defaultPackageNames =
-    [ "nano"
-      "perl"
+    [ "perl"
       "rsync"
       "strace"
     ];
@@ -90,12 +89,6 @@ in
           for a running system, entries can be removed for a more
           minimal NixOS installation.
 
-          Note: If `pkgs.nano` is removed from this list,
-          make sure another editor is installed and the
-          `EDITOR` environment variable is set to it.
-          Environment variables can be set using
-          {option}`environment.variables`.
-
           Like with systemPackages, packages are installed to
           {file}`/run/current-system/sw`. They are
           automatically available to all users, and are
@@ -116,8 +109,14 @@ in
       extraOutputsToInstall = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        example = [ "doc" "info" "devdoc" ];
-        description = lib.mdDoc "List of additional package outputs to be symlinked into {file}`/run/current-system/sw`.";
+        example = [ "dev" "info" ];
+        description = lib.mdDoc ''
+          Entries listed here will be appended to the `meta.outputsToInstall` attribute for each package in `environment.systemPackages`, and the files from the corresponding derivation outputs symlinked into {file}`/run/current-system/sw`.
+
+          For example, this can be used to install the `dev` and `info` outputs for all packages in the system environment, if they are available.
+
+          To use specific outputs instead of configuring them globally, select the corresponding attribute on the package derivation, e.g. `libxml2.dev` or `coreutils.info`.
+        '';
       };
 
       extraSetup = mkOption {
diff --git a/nixos/modules/config/terminfo.nix b/nixos/modules/config/terminfo.nix
index d1dbc4e0d05..ebd1aaea8f0 100644
--- a/nixos/modules/config/terminfo.nix
+++ b/nixos/modules/config/terminfo.nix
@@ -16,10 +16,7 @@ with lib;
     };
 
     security.sudo.keepTerminfo = mkOption {
-      default = config.security.sudo.package.pname != "sudo-rs";
-      defaultText = literalMD ''
-        `true` unless using `sudo-rs`
-      '';
+      default = true;
       type = types.bool;
       description = lib.mdDoc ''
         Whether to preserve the `TERMINFO` and `TERMINFO_DIRS`
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 785084209b0..39aac9fb821 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -153,7 +153,7 @@ let
           {file}`pam_mount.conf.xml`.
           Useful attributes might include `path`,
           `options`, `fstype`, and `server`.
-          See <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>
+          See <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>
           for more information.
         '';
       };
@@ -172,6 +172,17 @@ let
         '';
       };
 
+      ignoreShellProgramCheck = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          By default, nixos will check that programs.SHELL.enable is set to
+          true if the user has a custom shell specified. If that behavior isn't
+          required and there are custom overrides in place to make sure that the
+          shell is functional, set this to true.
+        '';
+      };
+
       subUidRanges = mkOption {
         type = with types; listOf (submodule subordinateUidRange);
         default = [];
@@ -330,6 +341,20 @@ let
           administrator before being able to use the system again.
         '';
       };
+
+      linger = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to enable lingering for this user. If true, systemd user
+          units will start at boot, rather than starting at login and stopping
+          at logout. This is the declarative equivalent of running
+          `loginctl enable-linger` for this user.
+
+          If false, user units will not be started until the user logs in, and
+          may be stopped on logout depending on the settings in `logind.conf`.
+        '';
+      };
     };
 
     config = mkMerge
@@ -449,6 +474,8 @@ let
   gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
   sdInitrdUidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) config.boot.initrd.systemd.users) "uid";
   sdInitrdGidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) config.boot.initrd.systemd.groups) "gid";
+  groupNames = lib.mapAttrsToList (n: g: g.name) cfg.groups;
+  usersWithoutExistingGroup = lib.filterAttrs (n: u: !lib.elem u.group groupNames) cfg.users;
 
   spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
     inherit (cfg) mutableUsers;
@@ -579,6 +606,14 @@ in {
           defaultText = literalExpression "config.users.users.\${name}.group";
           default = cfg.users.${name}.group;
         };
+        options.shell = mkOption {
+          type = types.passwdEntry types.path;
+          description = ''
+            The path to the user's shell in initrd.
+          '';
+          default = "${pkgs.shadow}/bin/nologin";
+          defaultText = literalExpression "\${pkgs.shadow}/bin/nologin";
+        };
       }));
     };
 
@@ -661,6 +696,20 @@ in {
       '';
     };
 
+    system.activationScripts.update-lingering = let
+      lingerDir = "/var/lib/systemd/linger";
+      lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger)));
+      lingeringUsersFile = builtins.toFile "lingering-users"
+        (concatStrings (map (s: "${s}\n")
+          (sort (a: b: a < b) lingeringUsers)));  # this sorting is important for `comm` to work correctly
+    in stringAfter [ "users" ] ''
+      if [ -e ${lingerDir} ] ; then
+        cd ${lingerDir}
+        ls ${lingerDir} | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger
+        ls ${lingerDir} | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl  enable-linger
+      fi
+    '';
+
     # Warn about user accounts with deprecated password hashing schemes
     system.activationScripts.hashes = {
       deps = [ "users" ];
@@ -700,7 +749,8 @@ in {
 
     environment.profiles = [
       "$HOME/.nix-profile"
-      "\${XDG_STATE_HOME:-$HOME/.local/state}/nix/profile"
+      "\${XDG_STATE_HOME}/nix/profile"
+      "$HOME/.local/state/nix/profile"
       "/etc/profiles/per-user/$USER"
     ];
 
@@ -708,17 +758,20 @@ in {
     boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
       contents = {
         "/etc/passwd".text = ''
-          ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group }: let
+          ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group, shell }: let
             g = config.boot.initrd.systemd.groups.${group};
-          in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:") config.boot.initrd.systemd.users)}
+          in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:${shell}") config.boot.initrd.systemd.users)}
         '';
         "/etc/group".text = ''
           ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
         '';
+        "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n";
       };
 
+      storePaths = [ "${pkgs.shadow}/bin/nologin" ];
+
       users = {
-        root = {};
+        root = { shell = lib.mkDefault "/bin/bash"; };
         nobody = {};
       };
 
@@ -750,6 +803,18 @@ in {
       { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
         message = "systemd initrd UIDs and GIDs must be unique!";
       }
+      { assertion = usersWithoutExistingGroup == {};
+        message =
+          let
+            errUsers = lib.attrNames usersWithoutExistingGroup;
+            missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup);
+            mkConfigHint = group: "users.groups.${group} = {};";
+          in ''
+            The following users have a primary group that is undefined: ${lib.concatStringsSep " " errUsers}
+            Hint: Add this to your NixOS configuration:
+              ${lib.concatStringsSep "\n  " (map mkConfigHint missingGroups)}
+          '';
+      }
       { # If mutableUsers is false, to prevent users creating a
         # configuration that locks them out of the system, ensure that
         # there is at least one "privileged" account that has a
@@ -810,13 +875,17 @@ in {
             '';
           }
         ] ++ (map (shell: {
-            assertion = (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
+            assertion = !user.ignoreShellProgramCheck -> (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
             message = ''
               users.users.${user.name}.shell is set to ${shell}, but
               programs.${shell}.enable is not true. This will cause the ${shell}
               shell to lack the basic nix directories in its PATH and might make
               logging in as that user impossible. You can fix it with:
               programs.${shell}.enable = true;
+
+              If you know what you're doing and you are fine with the behavior,
+              set users.users.${user.name}.ignoreShellProgramCheck = true;
+              instead.
             '';
           }) [
           "fish"
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 9e7a01c58af..6f58e848b38 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -18,29 +18,16 @@ in {
 
   options = {
 
-    hardware.enableAllFirmware = mkOption {
-      default = false;
-      type = types.bool;
-      description = lib.mdDoc ''
-        Turn on this option if you want to enable all the firmware.
-      '';
-    };
+    hardware.enableAllFirmware = mkEnableOption "all firmware regardless of license";
 
-    hardware.enableRedistributableFirmware = mkOption {
+    hardware.enableRedistributableFirmware = mkEnableOption "firmware with a license allowing redistribution" // {
       default = config.hardware.enableAllFirmware;
       defaultText = lib.literalExpression "config.hardware.enableAllFirmware";
-      type = types.bool;
-      description = lib.mdDoc ''
-        Turn on this option if you want to enable all the firmware with a license allowing redistribution.
-      '';
     };
 
-    hardware.wirelessRegulatoryDatabase = mkOption {
-      default = false;
-      type = types.bool;
-      description = lib.mdDoc ''
-        Load the wireless regulatory database at boot.
-      '';
+    hardware.wirelessRegulatoryDatabase = mkEnableOption "loading the wireless regulatory database at boot" // {
+      default = cfg.enableRedistributableFirmware || cfg.enableAllFirmware;
+      defaultText = literalMD "Enabled if proprietary firmware is allowed via {option}`enableRedistributableFirmware` or {option}`enableAllFirmware`.";
     };
 
   };
@@ -65,11 +52,10 @@ in {
         ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
         rtl8723bs-firmware
       ];
-      hardware.wirelessRegulatoryDatabase = true;
     })
     (mkIf cfg.enableAllFirmware {
       assertions = [{
-        assertion = !cfg.enableAllFirmware || config.nixpkgs.config.allowUnfree;
+        assertion = !cfg.enableAllFirmware || pkgs.config.allowUnfree;
         message = ''
           the list of hardware.enableAllFirmware contains non-redistributable licensed firmware files.
             This requires nixpkgs.config.allowUnfree to be true.
diff --git a/nixos/modules/hardware/corectrl.nix b/nixos/modules/hardware/corectrl.nix
index 965cbe0267e..8ef61a158d5 100644
--- a/nixos/modules/hardware/corectrl.nix
+++ b/nixos/modules/hardware/corectrl.nix
@@ -8,13 +8,13 @@ in
 {
   options.programs.corectrl = {
     enable = mkEnableOption (lib.mdDoc ''
-      A tool to overclock amd graphics cards and processors.
+      CoreCtrl, a tool to overclock amd graphics cards and processors.
       Add your user to the corectrl group to run corectrl without needing to enter your password
     '');
 
     gpuOverclock = {
       enable = mkEnableOption (lib.mdDoc ''
-        true
+        GPU overclocking
       '');
       ppfeaturemask = mkOption {
         type = types.str;
diff --git a/nixos/modules/hardware/cpu/x86-msr.nix b/nixos/modules/hardware/cpu/x86-msr.nix
new file mode 100644
index 00000000000..554bec1b7db
--- /dev/null
+++ b/nixos/modules/hardware/cpu/x86-msr.nix
@@ -0,0 +1,91 @@
+{ lib
+, config
+, options
+, ...
+}:
+let
+  inherit (builtins) hasAttr;
+  inherit (lib) mkIf mdDoc;
+  cfg = config.hardware.cpu.x86.msr;
+  opt = options.hardware.cpu.x86.msr;
+  defaultGroup = "msr";
+  isDefaultGroup = cfg.group == defaultGroup;
+  set = "to set for devices of the `msr` kernel subsystem.";
+
+  # Generates `foo=bar` parameters to pass to the kernel.
+  # If `module = baz` is passed, generates `baz.foo=bar`.
+  # Adds double quotes on demand to handle `foo="bar baz"`.
+  kernelParam = { module ? null }: name: value:
+    assert lib.asserts.assertMsg (!lib.strings.hasInfix "=" name) "kernel parameter cannot have '=' in name";
+    let
+      key = (if module == null then "" else module + ".") + name;
+      valueString = lib.generators.mkValueStringDefault {} value;
+      quotedValueString = if lib.strings.hasInfix " " valueString
+        then lib.strings.escape ["\""] valueString
+        else valueString;
+    in "${key}=${quotedValueString}";
+  msrKernelParam = kernelParam { module = "msr"; };
+in
+{
+  options.hardware.cpu.x86.msr = with lib.options; with lib.types; {
+    enable = mkEnableOption (mdDoc "the `msr` (Model-Specific Registers) kernel module and configure `udev` rules for its devices (usually `/dev/cpu/*/msr`)");
+    owner = mkOption {
+      type = str;
+      default = "root";
+      example = "nobody";
+      description = mdDoc "Owner ${set}";
+    };
+    group = mkOption {
+      type = str;
+      default = defaultGroup;
+      example = "nobody";
+      description = mdDoc "Group ${set}";
+    };
+    mode = mkOption {
+      type = str;
+      default = "0640";
+      example = "0660";
+      description = mdDoc "Mode ${set}";
+    };
+    settings = mkOption {
+      type = submodule {
+        freeformType = attrsOf (oneOf [ bool int str ]);
+        options.allow-writes = mkOption {
+          type = nullOr (enum ["on" "off"]);
+          default = null;
+          description = "Whether to allow writes to MSRs (`\"on\"`) or not (`\"off\"`).";
+        };
+      };
+      default = {};
+      description = "Parameters for the `msr` kernel module.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = hasAttr cfg.owner config.users.users;
+        message = "Owner '${cfg.owner}' set in `${opt.owner}` is not configured via `${options.users.users}.\"${cfg.owner}\"`.";
+      }
+      {
+        assertion = isDefaultGroup || (hasAttr cfg.group config.users.groups);
+        message = "Group '${cfg.group}' set in `${opt.group}` is not configured via `${options.users.groups}.\"${cfg.group}\"`.";
+      }
+    ];
+
+    boot = {
+      kernelModules = [ "msr" ];
+      kernelParams = lib.attrsets.mapAttrsToList msrKernelParam (lib.attrsets.filterAttrs (_: value: value != null) cfg.settings);
+    };
+
+    users.groups.${cfg.group} = mkIf isDefaultGroup { };
+
+    services.udev.extraRules = ''
+      SUBSYSTEM=="msr", OWNER="${cfg.owner}", GROUP="${cfg.group}", MODE="${cfg.mode}"
+    '';
+  };
+
+  meta = with lib; {
+    maintainers = with maintainers; [ lorenzleutgeb ];
+  };
+}
diff --git a/nixos/modules/hardware/device-tree.nix b/nixos/modules/hardware/device-tree.nix
index c568f52ab67..6ab13c0eb70 100644
--- a/nixos/modules/hardware/device-tree.nix
+++ b/nixos/modules/hardware/device-tree.nix
@@ -66,36 +66,32 @@ let
   };
 
   filterDTBs = src: if cfg.filter == null
-    then "${src}/dtbs"
+    then src
     else
       pkgs.runCommand "dtbs-filtered" {} ''
         mkdir -p $out
-        cd ${src}/dtbs
+        cd ${src}
         find . -type f -name '${cfg.filter}' -print0 \
           | xargs -0 cp -v --no-preserve=mode --target-directory $out --parents
       '';
 
-  filteredDTBs = filterDTBs cfg.kernelPackage;
-
-  # Compile single Device Tree overlay source
-  # file (.dts) into its compiled variant (.dtbo)
-  compileDTS = name: f: pkgs.callPackage({ stdenv, dtc }: stdenv.mkDerivation {
-    name = "${name}-dtbo";
-
-    nativeBuildInputs = [ dtc ];
-
-    buildCommand = ''
-      $CC -E -nostdinc -I${getDev cfg.kernelPackage}/lib/modules/${cfg.kernelPackage.modDirVersion}/source/scripts/dtc/include-prefixes -undef -D__DTS__ -x assembler-with-cpp ${f} | \
-        dtc -I dts -O dtb -@ -o $out
-    '';
-  }) {};
+  filteredDTBs = filterDTBs cfg.dtbSource;
 
   # Fill in `dtboFile` for each overlay if not set already.
   # Existence of one of these is guarded by assertion below
   withDTBOs = xs: flip map xs (o: o // { dtboFile =
+    let
+      includePaths = ["${getDev cfg.kernelPackage}/lib/modules/${cfg.kernelPackage.modDirVersion}/source/scripts/dtc/include-prefixes"] ++ cfg.dtboBuildExtraIncludePaths;
+      extraPreprocessorFlags = cfg.dtboBuildExtraPreprocessorFlags;
+    in
     if o.dtboFile == null then
-      if o.dtsFile != null then compileDTS o.name o.dtsFile
-      else compileDTS o.name (pkgs.writeText "dts" o.dtsText)
+      let
+        dtsFile = if o.dtsFile == null then (pkgs.writeText "dts" o.dtsText) else o.dtsFile;
+      in
+      pkgs.deviceTree.compileDTS {
+        name = "${o.name}-dtbo";
+        inherit includePaths extraPreprocessorFlags dtsFile;
+      }
     else o.dtboFile; } );
 
 in
@@ -121,7 +117,39 @@ in
           example = literalExpression "pkgs.linux_latest";
           type = types.path;
           description = lib.mdDoc ''
-            Kernel package containing the base device-tree (.dtb) to boot. Uses
+            Kernel package where device tree include directory is from. Also used as default source of dtb package to apply overlays to
+          '';
+        };
+
+        dtboBuildExtraPreprocessorFlags = mkOption {
+          default = [];
+          example = literalExpression "[ \"-DMY_DTB_DEFINE\" ]";
+          type = types.listOf types.str;
+          description = lib.mdDoc ''
+            Additional flags to pass to the preprocessor during dtbo compilations
+          '';
+        };
+
+        dtboBuildExtraIncludePaths = mkOption {
+          default = [];
+          example = literalExpression ''
+            [
+              ./my_custom_include_dir_1
+              ./custom_include_dir_2
+            ]
+          '';
+          type = types.listOf types.path;
+          description = lib.mdDoc ''
+            Additional include paths that will be passed to the preprocessor when creating the final .dts to compile into .dtbo
+          '';
+        };
+
+        dtbSource = mkOption {
+          default = "${cfg.kernelPackage}/dtbs";
+          defaultText = literalExpression "\${cfg.kernelPackage}/dtbs";
+          type = types.path;
+          description = lib.mdDoc ''
+            Path to dtb directory that overlays and other processing will be applied to. Uses
             device trees bundled with the Linux kernel by default.
           '';
         };
diff --git a/nixos/modules/hardware/i2c.nix b/nixos/modules/hardware/i2c.nix
index 9a5a2e44813..bd4c4ebe21b 100644
--- a/nixos/modules/hardware/i2c.nix
+++ b/nixos/modules/hardware/i2c.nix
@@ -11,7 +11,7 @@ in
     enable = mkEnableOption (lib.mdDoc ''
       i2c devices support. By default access is granted to users in the "i2c"
       group (will be created if non-existent) and any user with a seat, meaning
-      logged on the computer locally.
+      logged on the computer locally
     '');
 
     group = mkOption {
diff --git a/nixos/modules/hardware/keyboard/uhk.nix b/nixos/modules/hardware/keyboard/uhk.nix
index 17baff83d88..ff984fa5daa 100644
--- a/nixos/modules/hardware/keyboard/uhk.nix
+++ b/nixos/modules/hardware/keyboard/uhk.nix
@@ -11,7 +11,7 @@ in
       non-root access to the firmware of UHK keyboards.
       You need it when you want to flash a new firmware on the keyboard.
       Access to the keyboard is granted to users in the "input" group.
-      You may want to install the uhk-agent package.
+      You may want to install the uhk-agent package
     '');
 
   };
diff --git a/nixos/modules/hardware/keyboard/zsa.nix b/nixos/modules/hardware/keyboard/zsa.nix
index a04b67b5c8d..191fb12cca4 100644
--- a/nixos/modules/hardware/keyboard/zsa.nix
+++ b/nixos/modules/hardware/keyboard/zsa.nix
@@ -11,7 +11,7 @@ in
       udev rules for keyboards from ZSA like the ErgoDox EZ, Planck EZ and Moonlander Mark I.
       You need it when you want to flash a new configuration on the keyboard
       or use their live training in the browser.
-      You may want to install the wally-cli package.
+      You may want to install the wally-cli package
     '');
   };
 
diff --git a/nixos/modules/hardware/openrazer.nix b/nixos/modules/hardware/openrazer.nix
index aaa4000e758..abbafaee895 100644
--- a/nixos/modules/hardware/openrazer.nix
+++ b/nixos/modules/hardware/openrazer.nix
@@ -50,7 +50,7 @@ in
   options = {
     hardware.openrazer = {
       enable = mkEnableOption (lib.mdDoc ''
-        OpenRazer drivers and userspace daemon.
+        OpenRazer drivers and userspace daemon
       '');
 
       verboseLogging = mkOption {
diff --git a/nixos/modules/hardware/tuxedo-keyboard.nix b/nixos/modules/hardware/tuxedo-keyboard.nix
index 3ae876bd1f1..fd8b48a5e9e 100644
--- a/nixos/modules/hardware/tuxedo-keyboard.nix
+++ b/nixos/modules/hardware/tuxedo-keyboard.nix
@@ -9,7 +9,7 @@ in
   {
     options.hardware.tuxedo-keyboard = {
       enable = mkEnableOption (lib.mdDoc ''
-          Enables the tuxedo-keyboard driver.
+          the tuxedo-keyboard driver.
 
           To configure the driver, pass the options to the {option}`boot.kernelParams` configuration.
           There are several parameters you can change. It's best to check at the source code description which options are supported.
diff --git a/nixos/modules/hardware/video/amdgpu-pro.nix b/nixos/modules/hardware/video/amdgpu-pro.nix
index 299a30b0629..605aa6ef8b8 100644
--- a/nixos/modules/hardware/video/amdgpu-pro.nix
+++ b/nixos/modules/hardware/video/amdgpu-pro.nix
@@ -20,9 +20,6 @@ in
 {
 
   config = mkIf enabled {
-
-    nixpkgs.config.xorg.abiCompat = "1.20";
-
     services.xserver.drivers = singleton
       { name = "amdgpu"; modules = [ package ]; display = true; };
 
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index a40713ac25c..c36775dd24b 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -24,7 +24,7 @@ in {
   options = {
     hardware.nvidia = {
       datacenter.enable = lib.mkEnableOption (lib.mdDoc ''
-        Data Center drivers for NVIDIA cards on a NVLink topology.
+        Data Center drivers for NVIDIA cards on a NVLink topology
       '');
       datacenter.settings = lib.mkOption {
         type = settingsFormat.type;
@@ -79,18 +79,18 @@ in {
 
       powerManagement.enable = lib.mkEnableOption (lib.mdDoc ''
         experimental power management through systemd. For more information, see
-        the NVIDIA docs, on Chapter 21. Configuring Power Management Support.
+        the NVIDIA docs, on Chapter 21. Configuring Power Management Support
       '');
 
       powerManagement.finegrained = lib.mkEnableOption (lib.mdDoc ''
         experimental power management of PRIME offload. For more information, see
-        the NVIDIA docs, on Chapter 22. PCI-Express Runtime D3 (RTD3) Power Management.
+        the NVIDIA docs, on Chapter 22. PCI-Express Runtime D3 (RTD3) Power Management
       '');
 
       dynamicBoost.enable = lib.mkEnableOption (lib.mdDoc ''
         dynamic Boost balances power between the CPU and the GPU for improved
         performance on supported laptops using the nvidia-powerd daemon. For more
-        information, see the NVIDIA docs, on Chapter 23. Dynamic Boost on Linux.
+        information, see the NVIDIA docs, on Chapter 23. Dynamic Boost on Linux
       '');
 
       modesetting.enable = lib.mkEnableOption (lib.mdDoc ''
@@ -99,7 +99,7 @@ in {
         Enabling this fixes screen tearing when using Optimus via PRIME (see
         {option}`hardware.nvidia.prime.sync.enable`. This is not enabled
         by default because it is not officially supported by NVIDIA and would not
-        work with SLI.
+        work with SLI
       '');
 
       prime.nvidiaBusId = lib.mkOption {
@@ -153,11 +153,11 @@ in {
 
         Note that this configuration will only be successful when a display manager
         for which the {option}`services.xserver.displayManager.setupCommands`
-        option is supported is used.
+        option is supported is used
       '');
 
       prime.allowExternalGpu = lib.mkEnableOption (lib.mdDoc ''
-        configuring X to allow external NVIDIA GPUs when using Prime [Reverse] sync optimus.
+        configuring X to allow external NVIDIA GPUs when using Prime [Reverse] sync optimus
       '');
 
       prime.offload.enable = lib.mkEnableOption (lib.mdDoc ''
@@ -166,7 +166,7 @@ in {
         If this is enabled, then the bus IDs of the NVIDIA and Intel/AMD GPUs have to
         be specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
         {option}`hardware.nvidia.prime.intelBusId` or
-        {option}`hardware.nvidia.prime.amdgpuBusId`).
+        {option}`hardware.nvidia.prime.amdgpuBusId`)
       '');
 
       prime.offload.enableOffloadCmd = lib.mkEnableOption (lib.mdDoc ''
@@ -174,7 +174,7 @@ in {
         for offloading programs to an nvidia device. To work, should have also enabled
         {option}`hardware.nvidia.prime.offload.enable` or {option}`hardware.nvidia.prime.reverseSync.enable`.
 
-        Example usage `nvidia-offload sauerbraten_client`.
+        Example usage `nvidia-offload sauerbraten_client`
       '');
 
       prime.reverseSync.enable = lib.mkEnableOption (lib.mdDoc ''
@@ -202,25 +202,25 @@ in {
 
         Note that this configuration will only be successful when a display manager
         for which the {option}`services.xserver.displayManager.setupCommands`
-        option is supported is used.
+        option is supported is used
       '');
 
       nvidiaSettings =
         (lib.mkEnableOption (lib.mdDoc ''
-          nvidia-settings, NVIDIA's GUI configuration tool.
+          nvidia-settings, NVIDIA's GUI configuration tool
         ''))
         // {default = true;};
 
       nvidiaPersistenced = lib.mkEnableOption (lib.mdDoc ''
         nvidia-persistenced a update for NVIDIA GPU headless mode, i.e.
-        It ensures all GPUs stay awake even during headless mode.
+        It ensures all GPUs stay awake even during headless mode
       '');
 
       forceFullCompositionPipeline = lib.mkEnableOption (lib.mdDoc ''
         forcefully the full composition pipeline.
         This sometimes fixes screen tearing issues.
         This has been reported to reduce the performance of some OpenGL applications and may produce issues in WebGL.
-        It also drastically increases the time the driver needs to clock down after load.
+        It also drastically increases the time the driver needs to clock down after load
       '');
 
       package = lib.mkOption {
@@ -269,9 +269,9 @@ in {
         services.udev.extraRules =
         ''
           # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
-          KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) 255'"
-          KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'for i in $$(cat /proc/driver/nvidia/gpus/*/information | grep Minor | cut -d \  -f 4); do mknod -m 666 /dev/nvidia$${i} c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) $${i}; done'"
-          KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) 254'"
+          KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c 195 255'"
+          KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'for i in $$(cat /proc/driver/nvidia/gpus/*/information | grep Minor | cut -d \  -f 4); do mknod -m 666 /dev/nvidia$${i} c 195 $${i}; done'"
+          KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c 195 254'"
           KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
           KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm-tools c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 1'"
         '';
diff --git a/nixos/modules/hardware/video/webcam/facetimehd.nix b/nixos/modules/hardware/video/webcam/facetimehd.nix
index 480c636aa0d..a0ec9c98a54 100644
--- a/nixos/modules/hardware/video/webcam/facetimehd.nix
+++ b/nixos/modules/hardware/video/webcam/facetimehd.nix
@@ -12,7 +12,7 @@ in
 
 {
 
-  options.hardware.facetimehd.enable = mkEnableOption (lib.mdDoc "facetimehd kernel module");
+  options.hardware.facetimehd.enable = mkEnableOption (lib.mdDoc "the facetimehd kernel module");
 
   options.hardware.facetimehd.withCalibration = mkOption {
     default = false;
diff --git a/nixos/modules/image/repart.md b/nixos/modules/image/repart.md
deleted file mode 100644
index 6d0675f21a0..00000000000
--- a/nixos/modules/image/repart.md
+++ /dev/null
@@ -1,137 +0,0 @@
-# Building Images via `systemd-repart` {#sec-image-repart}
-
-You can build disk images in NixOS with the `image.repart` option provided by
-the module [image/repart.nix][]. This module uses `systemd-repart` to build the
-images and exposes it's entire interface via the `repartConfig` option.
-
-[image/repart.nix]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/image/repart.nix
-
-An example of how to build an image:
-
-```nix
-{ config, modulesPath, ... }: {
-
-  imports = [ "${modulesPath}/image/repart.nix" ];
-
-  image.repart = {
-    name = "image";
-    partitions = {
-      "esp" = {
-        contents = {
-          ...
-        };
-        repartConfig = {
-          Type = "esp";
-          ...
-        };
-      };
-      "root" = {
-        storePaths = [ config.system.build.toplevel ];
-        repartConfig = {
-          Type = "root";
-          Label = "nixos";
-          ...
-        };
-      };
-    };
-  };
-
-}
-```
-
-## Nix Store Partition {#sec-image-repart-store-partition}
-
-You can define a partition that only contains the Nix store and then mount it
-under `/nix/store`. Because the `/nix/store` part of the paths is already
-determined by the mount point, you have to set `stripNixStorePrefix = true;` so
-that the prefix is stripped from the paths before copying them into the image.
-
-```nix
-fileSystems."/nix/store".device = "/dev/disk/by-partlabel/nix-store"
-
-image.repart.partitions = {
-  "store" = {
-    storePaths = [ config.system.build.toplevel ];
-    stripNixStorePrefix = true;
-    repartConfig = {
-      Type = "linux-generic";
-      Label = "nix-store";
-      ...
-    };
-  };
-};
-```
-
-## Appliance Image {#sec-image-repart-appliance}
-
-The `image/repart.nix` module can also be used to build self-contained [software
-appliances][].
-
-[software appliances]: https://en.wikipedia.org/wiki/Software_appliance
-
-The generation based update mechanism of NixOS is not suited for appliances.
-Updates of appliances are usually either performed by replacing the entire
-image with a new one or by updating partitions via an A/B scheme. See the
-[Chrome OS update process][chrome-os-update] for an example of how to achieve
-this. The appliance image built in the following example does not contain a
-`configuration.nix` and thus you will not be able to call `nixos-rebuild` from
-this system.
-
-[chrome-os-update]: https://chromium.googlesource.com/aosp/platform/system/update_engine/+/HEAD/README.md
-
-```nix
-let
-  pkgs = import <nixpkgs> { };
-  efiArch = pkgs.stdenv.hostPlatform.efiArch;
-in
-(pkgs.nixos [
-  ({ config, lib, pkgs, modulesPath, ... }: {
-
-    imports = [ "${modulesPath}/image/repart.nix" ];
-
-    boot.loader.grub.enable = false;
-
-    fileSystems."/".device = "/dev/disk/by-label/nixos";
-
-    image.repart = {
-      name = "image";
-      partitions = {
-        "esp" = {
-          contents = {
-            "/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source =
-              "${pkgs.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi";
-
-            "/loader/entries/nixos.conf".source = pkgs.writeText "nixos.conf" ''
-              title NixOS
-              linux /EFI/nixos/kernel.efi
-              initrd /EFI/nixos/initrd.efi
-              options init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
-            '';
-
-            "/EFI/nixos/kernel.efi".source =
-              "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
-
-            "/EFI/nixos/initrd.efi".source =
-              "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
-          };
-          repartConfig = {
-            Type = "esp";
-            Format = "vfat";
-            SizeMinBytes = "96M";
-          };
-        };
-        "root" = {
-          storePaths = [ config.system.build.toplevel ];
-          repartConfig = {
-            Type = "root";
-            Format = "ext4";
-            Label = "nixos";
-            Minimize = "guess";
-          };
-        };
-      };
-    };
-
-  })
-]).image
-```
diff --git a/nixos/modules/image/repart.nix b/nixos/modules/image/repart.nix
index 4a0021e9a56..41e6110885b 100644
--- a/nixos/modules/image/repart.nix
+++ b/nixos/modules/image/repart.nix
@@ -34,12 +34,13 @@ let
           };
         });
         default = { };
-        example = lib.literalExpression '' {
-          "/EFI/BOOT/BOOTX64.EFI".source =
-            "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
+        example = lib.literalExpression ''
+          {
+            "/EFI/BOOT/BOOTX64.EFI".source =
+              "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
 
-          "/loader/entries/nixos.conf".source = systemdBootEntry;
-        }
+            "/loader/entries/nixos.conf".source = systemdBootEntry;
+          }
         '';
         description = lib.mdDoc "The contents to end up in the filesystem image.";
       };
@@ -90,34 +91,33 @@ in
 
     package = lib.mkPackageOption pkgs "systemd-repart" {
       default = "systemd";
-      example = lib.literalExpression ''
-        pkgs.systemdMinimal.override { withCryptsetup = true; }
-      '';
+      example = "pkgs.systemdMinimal.override { withCryptsetup = true; }";
     };
 
     partitions = lib.mkOption {
       type = with lib.types; attrsOf (submodule partitionOptions);
       default = { };
-      example = lib.literalExpression '' {
-        "10-esp" = {
-          contents = {
-            "/EFI/BOOT/BOOTX64.EFI".source =
-              "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
-          }
-          repartConfig = {
-            Type = "esp";
-            Format = "fat";
+      example = lib.literalExpression ''
+        {
+          "10-esp" = {
+            contents = {
+              "/EFI/BOOT/BOOTX64.EFI".source =
+                "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
+            }
+            repartConfig = {
+              Type = "esp";
+              Format = "fat";
+            };
           };
-        };
-        "20-root" = {
-          storePaths = [ config.system.build.toplevel ];
-          repartConfig = {
-            Type = "root";
-            Format = "ext4";
-            Minimize = "guess";
+          "20-root" = {
+            storePaths = [ config.system.build.toplevel ];
+            repartConfig = {
+              Type = "root";
+              Format = "ext4";
+              Minimize = "guess";
+            };
           };
         };
-      };
       '';
       description = lib.mdDoc ''
         Specify partitions as a set of the names of the partitions with their
@@ -188,6 +188,7 @@ in
           nativeBuildInputs = [
             cfg.package
             pkgs.fakeroot
+            pkgs.util-linux
           ] ++ fileSystemTools;
         } ''
         amendedRepartDefinitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory})
@@ -195,7 +196,7 @@ in
         mkdir -p $out
         cd $out
 
-        fakeroot systemd-repart \
+        unshare --map-root-user fakeroot systemd-repart \
           --dry-run=no \
           --empty=create \
           --size=auto \
@@ -207,10 +208,7 @@ in
           | tee repart-output.json
       '';
 
-    meta = {
-      maintainers = with lib.maintainers; [ nikstur ];
-      doc = ./repart.md;
-    };
+    meta.maintainers = with lib.maintainers; [ nikstur ];
 
   };
 }
diff --git a/nixos/modules/installer/cd-dvd/channel.nix b/nixos/modules/installer/cd-dvd/channel.nix
index 8426ba8fac0..bc70dc985fe 100644
--- a/nixos/modules/installer/cd-dvd/channel.nix
+++ b/nixos/modules/installer/cd-dvd/channel.nix
@@ -3,8 +3,6 @@
 
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   # This is copied into the installer image, so it's important that it is filtered
   # to avoid including a large .git directory.
@@ -27,38 +25,40 @@ let
       if [ ! -e $out/nixos/nixpkgs ]; then
         ln -s . $out/nixos/nixpkgs
       fi
-      ${optionalString (config.system.nixos.revision != null) ''
+      ${lib.optionalString (config.system.nixos.revision != null) ''
         echo -n ${config.system.nixos.revision} > $out/nixos/.git-revision
       ''}
       echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
       echo ${config.system.nixos.versionSuffix} | sed -e s/pre// > $out/nixos/svn-revision
     '';
-
 in
 
 {
-  # Pin the nixpkgs flake in the installer to our cleaned up nixpkgs source.
-  # FIXME: this might be surprising and is really only needed for offline installations,
-  # see discussion in https://github.com/NixOS/nixpkgs/pull/204178#issuecomment-1336289021
-  nix.registry.nixpkgs.to = {
-    type = "path";
-    path = "${channelSources}/nixos";
-  };
+  options.system.installer.channel.enable = (lib.mkEnableOption "bundling NixOS/Nixpkgs channel in the installer") // { default = true; };
+  config = lib.mkIf config.system.installer.channel.enable {
+    # Pin the nixpkgs flake in the installer to our cleaned up nixpkgs source.
+    # FIXME: this might be surprising and is really only needed for offline installations,
+    # see discussion in https://github.com/NixOS/nixpkgs/pull/204178#issuecomment-1336289021
+    nix.registry.nixpkgs.to = {
+      type = "path";
+      path = "${channelSources}/nixos";
+    };
 
-  # Provide the NixOS/Nixpkgs sources in /etc/nixos.  This is required
-  # for nixos-install.
-  boot.postBootCommands = mkAfter
-    ''
-      if ! [ -e /var/lib/nixos/did-channel-init ]; then
-        echo "unpacking the NixOS/Nixpkgs sources..."
-        mkdir -p /nix/var/nix/profiles/per-user/root
-        ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/per-user/root/channels \
-          -i ${channelSources} --quiet --option build-use-substitutes false \
-          ${optionalString config.boot.initrd.systemd.enable "--option sandbox false"} # There's an issue with pivot_root
-        mkdir -m 0700 -p /root/.nix-defexpr
-        ln -s /nix/var/nix/profiles/per-user/root/channels /root/.nix-defexpr/channels
-        mkdir -m 0755 -p /var/lib/nixos
-        touch /var/lib/nixos/did-channel-init
-      fi
-    '';
+    # Provide the NixOS/Nixpkgs sources in /etc/nixos.  This is required
+    # for nixos-install.
+    boot.postBootCommands = lib.mkAfter
+      ''
+        if ! [ -e /var/lib/nixos/did-channel-init ]; then
+          echo "unpacking the NixOS/Nixpkgs sources..."
+          mkdir -p /nix/var/nix/profiles/per-user/root
+          ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/per-user/root/channels \
+            -i ${channelSources} --quiet --option build-use-substitutes false \
+            ${lib.optionalString config.boot.initrd.systemd.enable "--option sandbox false"} # There's an issue with pivot_root
+          mkdir -m 0700 -p /root/.nix-defexpr
+          ln -s /nix/var/nix/profiles/per-user/root/channels /root/.nix-defexpr/channels
+          mkdir -m 0755 -p /var/lib/nixos
+          touch /var/lib/nixos/did-channel-init
+        fi
+      '';
+  };
 }
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 10c37a46fda..e4241e96540 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/3wqasl97rjiza3vd7fxjnvli2w9l30mk-nix-2.17.0";
-  i686-linux = "/nix/store/z360xswxfx55pmm1fng3hw748rbs0kkj-nix-2.17.0";
-  aarch64-linux = "/nix/store/9670sxa916xmv8n1kqs7cdvmnsrhrdjv-nix-2.17.0";
-  x86_64-darwin = "/nix/store/2rdbky9j8hc3mbgl6pnda4hkjllyfwnn-nix-2.17.0";
-  aarch64-darwin = "/nix/store/jl9qma14fb4zk9lq1k0syw2k9qm2gqjw-nix-2.17.0";
+  x86_64-linux = "/nix/store/azvn85cras6xv4z5j85fiy406f24r1q0-nix-2.18.1";
+  i686-linux = "/nix/store/9bnwy7f9h0kzdzmcnjjsjg0aak5waj40-nix-2.18.1";
+  aarch64-linux = "/nix/store/hh65xwqm9s040s3cgn9vzcmrxj0sf5ij-nix-2.18.1";
+  x86_64-darwin = "/nix/store/6zi5fqzn9n17wrk8r41rhdw4j7jqqsi3-nix-2.18.1";
+  aarch64-darwin = "/nix/store/0pbq6wzr2f1jgpn5212knyxpwmkjgjah-nix-2.18.1";
 }
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 7d0c5898e23..2f9edba4f0c 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -102,22 +102,6 @@ sub cpuManufacturer {
     return $cpuinfo =~ /^vendor_id\s*:.* $id$/m;
 }
 
-
-# Determine CPU governor to use
-if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
-    my $governors = read_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors");
-    # ondemand governor is not available on sandy bridge or later Intel CPUs
-    my @desired_governors = ("ondemand", "powersave");
-    my $e;
-
-    foreach $e (@desired_governors) {
-        if (index($governors, $e) != -1) {
-            last if (push @attrs, "powerManagement.cpuFreqGovernor = lib.mkDefault \"$e\";");
-        }
-    }
-}
-
-
 # Virtualization support?
 push @kernelModules, "kvm-intel" if hasCPUFeature "vmx";
 push @kernelModules, "kvm-amd" if hasCPUFeature "svm";
@@ -146,7 +130,7 @@ sub pciCheck {
     debug "\n";
 
     if (defined $module) {
-        # See the bottom of http://pciids.sourceforge.net/pci.ids for
+        # See the bottom of https://pciids.sourceforge.net/pci.ids for
         # device classes.
         if (# Mass-storage controller.  Definitely important.
             $class =~ /^0x01/ ||
@@ -273,6 +257,7 @@ foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
 
 # Add bcache module, if needed.
 my @bcacheDevices = glob("/dev/bcache*");
+@bcacheDevices = grep(!qr#dev/bcachefs.*#, @bcacheDevices);
 if (scalar @bcacheDevices > 0) {
     push @initrdAvailableKernelModules, "bcache";
 }
@@ -483,6 +468,19 @@ EOF
     # boot.tmp.useTmpfs option in configuration.nix (managed declaratively).
     next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");
 
+    # This should work for single and multi-device systems.
+    # still needs subvolume support
+    if ($fsType eq "bcachefs") {
+        my ($status, @info) = runCommand("bcachefs fs usage $rootDir$mountPoint");
+        my $UUID = $info[0];
+
+        if ($status == 0 && $UUID =~ /^Filesystem:[ \t\n]*([0-9a-z-]+)/) {
+            $stableDevPath = "UUID=$1";
+        } else {
+            print STDERR "warning: can't find bcachefs mount UUID falling back to device-path";
+        }
+    }
+
     # Emit the filesystem.
     $fileSystems .= <<EOF;
   fileSystems.\"$mountPoint\" =
diff --git a/nixos/modules/installer/tools/nixos-option/default.nix b/nixos/modules/installer/tools/nixos-option/default.nix
deleted file mode 100644
index 061460f38a3..00000000000
--- a/nixos/modules/installer/tools/nixos-option/default.nix
+++ /dev/null
@@ -1 +0,0 @@
-{ pkgs, ... }: pkgs.nixos-option
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 78bcbbe2db5..15e10128ac9 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -163,15 +163,15 @@ in
         # console = {
         #   font = "Lat2-Terminus16";
         #   keyMap = "us";
-        #   useXkbConfig = true; # use xkbOptions in tty.
+        #   useXkbConfig = true; # use xkb.options in tty.
         # };
 
       $xserverConfig
 
       $desktopConfiguration
         # Configure keymap in X11
-        # services.xserver.layout = "us";
-        # services.xserver.xkbOptions = "eurosign:e,caps:escape";
+        # services.xserver.xkb.layout = "us";
+        # services.xserver.xkb.options = "eurosign:e,caps:escape";
 
         # Enable CUPS to print documents.
         # services.printing.enable = true;
@@ -224,12 +224,22 @@ in
         # accidentally delete configuration.nix.
         # system.copySystemConfiguration = true;
 
-        # This value determines the NixOS release from which the default
-        # settings for stateful data, like file locations and database versions
-        # on your system were taken. It's perfectly fine and recommended to leave
-        # this value at the release version of the first install of this system.
-        # Before changing this value read the documentation for this option
-        # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
+        # This option defines the first version of NixOS you have installed on this particular machine,
+        # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
+        #
+        # Most users should NEVER change this value after the initial install, for any reason,
+        # even if you've upgraded your system to a new NixOS release.
+        #
+        # This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
+        # so changing it will NOT upgrade your system.
+        #
+        # This value being lower than the current NixOS release does NOT mean your system is
+        # out of date, out of support, or vulnerable.
+        #
+        # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
+        # and migrated your data accordingly.
+        #
+        # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
         system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment?
 
       }
diff --git a/nixos/modules/installer/virtualbox-demo.nix b/nixos/modules/installer/virtualbox-demo.nix
index 27a7651382b..01931b2acfc 100644
--- a/nixos/modules/installer/virtualbox-demo.nix
+++ b/nixos/modules/installer/virtualbox-demo.nix
@@ -21,7 +21,7 @@ with lib;
   services.xserver.videoDrivers = mkOverride 40 [ "virtualbox" "vmware" "cirrus" "vesa" "modesetting" ];
 
   powerManagement.enable = false;
-  system.stateVersion = mkDefault "18.03";
+  system.stateVersion = lib.mkDefault lib.trivial.release;
 
   installer.cloneConfigExtra = ''
   # Let demo build as a trusted user.
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index dc59ccb357d..18928a6bf21 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -69,7 +69,7 @@ in
       #dialout = 27; # unused
       polkituser = 28;
       #utmp = 29; # unused
-      # ddclient = 30; # software removed
+      # ddclient = 30; # converted to DynamicUser = true
       davfs2 = 31;
       disnix = 33;
       osgi = 34;
@@ -86,7 +86,7 @@ in
       #rtkit = 45; # dynamically allocated 2021-09-03
       dovecot2 = 46;
       dovenull2 = 47;
-      prayer = 49;
+      # prayer = 49; # dropped in 23.11
       mpd = 50;
       clamav = 51;
       #fprot = 52; # unused
@@ -394,7 +394,7 @@ in
       dialout = 27;
       #polkituser = 28; # currently unused, polkitd doesn't need a group
       utmp = 29;
-      # ddclient = 30; # software removed
+      # ddclient = 30; # converted to DynamicUser = true
       davfs2 = 31;
       disnix = 33;
       osgi = 34;
@@ -411,7 +411,7 @@ in
       #rtkit = 45; # unused
       dovecot2 = 46;
       dovenull2 = 47;
-      prayer = 49;
+      # prayer = 49; # dropped in 23.11
       mpd = 50;
       clamav = 51;
       #fprot = 52; # unused
diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix
index acf441cda62..3c76d17086b 100644
--- a/nixos/modules/misc/locate.nix
+++ b/nixos/modules/misc/locate.nix
@@ -4,14 +4,15 @@ with lib;
 
 let
   cfg = config.services.locate;
-  isMLocate = hasPrefix "mlocate" cfg.locate.name;
-  isPLocate = hasPrefix "plocate" cfg.locate.name;
-  isMorPLocate = (isMLocate || isPLocate);
-  isFindutils = hasPrefix "findutils" cfg.locate.name;
+  isMLocate = hasPrefix "mlocate" cfg.package.name;
+  isPLocate = hasPrefix "plocate" cfg.package.name;
+  isMorPLocate = isMLocate || isPLocate;
+  isFindutils = hasPrefix "findutils" cfg.package.name;
 in
 {
   imports = [
     (mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ])
+    (mkRenamedOptionModule [ "services" "locate" "locate" ] [ "services" "locate" "package" ])
     (mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths")
   ];
 
@@ -25,10 +26,10 @@ in
       '';
     };
 
-    locate = mkOption {
+    package = mkOption {
       type = package;
       default = pkgs.findutils.locate;
-      defaultText = literalExpression "pkgs.findutils";
+      defaultText = literalExpression "pkgs.findutils.locate";
       example = literalExpression "pkgs.mlocate";
       description = lib.mdDoc ''
         The locate implementation to use
@@ -216,25 +217,23 @@ in
           setgid = true;
           setuid = false;
         };
-        mlocate = (mkIf isMLocate {
+        mlocate = mkIf isMLocate {
           group = "mlocate";
-          source = "${cfg.locate}/bin/locate";
-        });
-        plocate = (mkIf isPLocate {
+          source = "${cfg.package}/bin/locate";
+        };
+        plocate = mkIf isPLocate {
           group = "plocate";
-          source = "${cfg.locate}/bin/plocate";
-        });
+          source = "${cfg.package}/bin/plocate";
+        };
       in
       mkIf isMorPLocate {
         locate = mkMerge [ common mlocate plocate ];
-        plocate = (mkIf isPLocate (mkMerge [ common plocate ]));
+        plocate = mkIf isPLocate (mkMerge [ common plocate ]);
       };
 
-    nixpkgs.config = { locate.dbfile = cfg.output; };
-
-    environment.systemPackages = [ cfg.locate ];
+    environment.systemPackages = [ cfg.package ];
 
-    environment.variables = mkIf (!isMorPLocate) { LOCATE_PATH = cfg.output; };
+    environment.variables.LOCATE_PATH = cfg.output;
 
     environment.etc = {
       # write /etc/updatedb.conf for manual calls to `updatedb`
@@ -270,13 +269,13 @@ in
             args = concatLists (map toFlags [ "pruneFS" "pruneNames" "prunePaths" ]);
           in
           ''
-            exec ${cfg.locate}/bin/updatedb \
+            exec ${cfg.package}/bin/updatedb \
               --output ${toString cfg.output} ${concatStringsSep " " args} \
               --prune-bind-mounts ${if cfg.pruneBindMounts then "yes" else "no"} \
               ${concatStringsSep " " cfg.extraFlags}
           ''
         else ''
-          exec ${cfg.locate}/bin/updatedb \
+          exec ${cfg.package}/bin/updatedb \
             ${optionalString (cfg.localuser != null && !isMorPLocate) "--localuser=${cfg.localuser}"} \
             --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags}
         '';
diff --git a/nixos/modules/misc/nixops-autoluks.nix b/nixos/modules/misc/nixops-autoluks.nix
index 221b34f3cc3..e6817633119 100644
--- a/nixos/modules/misc/nixops-autoluks.nix
+++ b/nixos/modules/misc/nixops-autoluks.nix
@@ -5,7 +5,7 @@ let
 
   inherit (config.nixops) enableDeprecatedAutoLuks;
 in {
-  options.nixops.enableDeprecatedAutoLuks = lib.mkEnableOption (lib.mdDoc "Enable the deprecated NixOps AutoLuks module");
+  options.nixops.enableDeprecatedAutoLuks = lib.mkEnableOption (lib.mdDoc "the deprecated NixOps AutoLuks module");
 
   config = {
     assertions = [
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index f9d8bccea28..da321a92344 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -176,22 +176,18 @@ in
         '';
       type = types.listOf overlayType;
       description = lib.mdDoc ''
-        List of overlays to use with the Nix Packages collection.
-        (For details, see the Nixpkgs documentation.)  It allows
-        you to override packages globally. Each function in the list
-        takes as an argument the *original* Nixpkgs.
-        The first argument should be used for finding dependencies, and
-        the second should be used for overriding recipes.
-
-        If `nixpkgs.pkgs` is set, overlays specified here
-        will be applied after the overlays that were already present
-        in `nixpkgs.pkgs`.
+        List of overlays to apply to Nixpkgs.
+        This option allows modifying the Nixpkgs package set accessed through the `pkgs` module argument.
+
+        For details, see the [Overlays chapter in the Nixpkgs manual](https://nixos.org/manual/nixpkgs/stable/#chap-overlays).
+
+        If the {option}`nixpkgs.pkgs` option is set, overlays specified using `nixpkgs.overlays` will be applied after the overlays that were already included in `nixpkgs.pkgs`.
       '';
     };
 
     hostPlatform = mkOption {
       type = types.either types.str types.attrs; # TODO utilize lib.systems.parsedPlatform
-      example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      example = { system = "aarch64-linux"; };
       # Make sure that the final value has all fields for sake of other modules
       # referring to this. TODO make `lib.systems` itself use the module system.
       apply = lib.systems.elaborate;
@@ -209,7 +205,7 @@ in
     buildPlatform = mkOption {
       type = types.either types.str types.attrs; # TODO utilize lib.systems.parsedPlatform
       default = cfg.hostPlatform;
-      example = { system = "x86_64-linux"; config = "x86_64-unknown-linux-gnu"; };
+      example = { system = "x86_64-linux"; };
       # Make sure that the final value has all fields for sake of other modules
       # referring to this.
       apply = lib.systems.elaborate;
@@ -232,7 +228,7 @@ in
     localSystem = mkOption {
       type = types.attrs; # TODO utilize lib.systems.parsedPlatform
       default = { inherit (cfg) system; };
-      example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      example = { system = "aarch64-linux"; };
       # Make sure that the final value has all fields for sake of other modules
       # referring to this. TODO make `lib.systems` itself use the module system.
       apply = lib.systems.elaborate;
@@ -266,7 +262,7 @@ in
     crossSystem = mkOption {
       type = types.nullOr types.attrs; # TODO utilize lib.systems.parsedPlatform
       default = null;
-      example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      example = { system = "aarch64-linux"; };
       description = lib.mdDoc ''
         Systems with a recently generated `hardware-configuration.nix`
         may instead specify *only* {option}`nixpkgs.buildPlatform`,
@@ -383,6 +379,16 @@ in
           the legacy definitions.
         '';
       }
+      {
+        assertion = opt.pkgs.isDefined -> cfg.config == {};
+        message = ''
+          Your system configures nixpkgs with an externally created instance.
+          `nixpkgs.config` options should be passed when creating the instance instead.
+
+          Current value:
+          ${lib.generators.toPretty { multiline = true; } opt.config}
+        '';
+      }
     ];
   };
 
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index 0a66eafe933..45dbf45b3ae 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -121,22 +121,32 @@ in
       default = cfg.release;
       defaultText = literalExpression "config.${opt.release}";
       description = lib.mdDoc ''
-        Every once in a while, a new NixOS release may change
-        configuration defaults in a way incompatible with stateful
-        data. For instance, if the default version of PostgreSQL
-        changes, the new version will probably be unable to read your
-        existing databases. To prevent such breakage, you should set the
-        value of this option to the NixOS release with which you want
-        to be compatible. The effect is that NixOS will use
-        defaults corresponding to the specified release (such as using
-        an older version of PostgreSQL).
-        It’s perfectly fine and recommended to leave this value at the
-        release version of the first install of this system.
-        Changing this option will not upgrade your system. In fact it
-        is meant to stay constant exactly when you upgrade your system.
-        You should only bump this option, if you are sure that you can
-        or have migrated all state on your system which is affected
-        by this option.
+        This option defines the first version of NixOS you have installed on this particular machine,
+        and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
+
+        For example, if NixOS version XX.YY ships with AwesomeDB version N by default, and is then
+        upgraded to version XX.YY+1, which ships AwesomeDB version N+1, the existing databases
+        may no longer be compatible, causing applications to fail, or even leading to data loss.
+
+        The `stateVersion` mechanism avoids this situation by making the default version of such packages
+        conditional on the first version of NixOS you've installed (encoded in `stateVersion`), instead of
+        simply always using the latest one.
+
+        Note that this generally only affects applications that can't upgrade their data automatically -
+        applications and services supporting automatic migrations will remain on latest versions when
+        you upgrade.
+
+        Most users should **never** change this value after the initial install, for any reason,
+        even if you've upgraded your system to a new NixOS release.
+
+        This value does **not** affect the Nixpkgs version your packages and OS are pulled from,
+        so changing it will **not** upgrade your system.
+
+        This value being lower than the current NixOS release does **not** mean your system is
+        out of date, out of support, or vulnerable.
+
+        Do **not** change this value unless you have manually inspected all the changes it would
+        make to your configuration, and migrated your data accordingly.
       '';
     };
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 206d5eaf75d..d02c5b593b3 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -2,11 +2,11 @@
   ./config/appstream.nix
   ./config/console.nix
   ./config/debug-info.nix
+  ./config/fanout.nix
   ./config/fonts/fontconfig.nix
   ./config/fonts/fontdir.nix
   ./config/fonts/ghostscript.nix
   ./config/fonts/packages.nix
-  ./config/gnu.nix
   ./config/gtk/gtk-icon-cache.nix
   ./config/i18n.nix
   ./config/iproute2.nix
@@ -55,6 +55,7 @@
   ./hardware/cpu/amd-sev.nix
   ./hardware/cpu/intel-microcode.nix
   ./hardware/cpu/intel-sgx.nix
+  ./hardware/cpu/x86-msr.nix
   ./hardware/decklink.nix
   ./hardware/device-tree.nix
   ./hardware/digitalbitbox.nix
@@ -232,6 +233,7 @@
   ./programs/pantheon-tweaks.nix
   ./programs/partition-manager.nix
   ./programs/plotinus.nix
+  ./programs/projecteur.nix
   ./programs/proxychains.nix
   ./programs/qdmr.nix
   ./programs/qt5ct.nix
@@ -265,7 +267,9 @@
   ./programs/udevil.nix
   ./programs/usbtop.nix
   ./programs/vim.nix
+  ./programs/virt-manager.nix
   ./programs/wavemon.nix
+  ./programs/wayland/cardboard.nix
   ./programs/wayland/river.nix
   ./programs/wayland/sway.nix
   ./programs/wayland/waybar.nix
@@ -324,6 +328,7 @@
   ./services/amqp/rabbitmq.nix
   ./services/audio/alsa.nix
   ./services/audio/botamusique.nix
+  ./services/audio/castopod.nix
   ./services/audio/gmediarender.nix
   ./services/audio/gonic.nix
   ./services/audio/goxlr-utility.nix
@@ -345,6 +350,7 @@
   ./services/audio/squeezelite.nix
   ./services/audio/tts.nix
   ./services/audio/wyoming/faster-whisper.nix
+  ./services/audio/wyoming/openwakeword.nix
   ./services/audio/wyoming/piper.nix
   ./services/audio/ympd.nix
   ./services/backup/automysqlbackup.nix
@@ -414,6 +420,7 @@
   ./services/databases/couchdb.nix
   ./services/databases/dgraph.nix
   ./services/databases/dragonflydb.nix
+  ./services/databases/ferretdb.nix
   ./services/databases/firebird.nix
   ./services/databases/foundationdb.nix
   ./services/databases/hbase-standalone.nix
@@ -479,6 +486,7 @@
   ./services/development/hoogle.nix
   ./services/development/jupyter/default.nix
   ./services/development/jupyterhub/default.nix
+  ./services/development/livebook.nix
   ./services/development/lorri.nix
   ./services/development/rstudio-server/default.nix
   ./services/development/zammad.nix
@@ -515,6 +523,7 @@
   ./services/hardware/hddfancontrol.nix
   ./services/hardware/illum.nix
   ./services/hardware/interception-tools.nix
+  ./services/hardware/iptsd.nix
   ./services/hardware/irqbalance.nix
   ./services/hardware/joycond.nix
   ./services/hardware/kanata.nix
@@ -540,6 +549,7 @@
   ./services/hardware/tlp.nix
   ./services/hardware/trezord.nix
   ./services/hardware/triggerhappy.nix
+  ./services/hardware/tuxedo-rs.nix
   ./services/hardware/udev.nix
   ./services/hardware/udisks2.nix
   ./services/hardware/undervolt.nix
@@ -552,7 +562,9 @@
   ./services/home-automation/esphome.nix
   ./services/home-automation/evcc.nix
   ./services/home-automation/home-assistant.nix
+  ./services/home-automation/homeassistant-satellite.nix
   ./services/home-automation/zigbee2mqtt.nix
+  ./services/home-automation/zwave-js.nix
   ./services/logging/SystemdJournal2Gelf.nix
   ./services/logging/awstats.nix
   ./services/logging/filebeat.nix
@@ -617,6 +629,7 @@
   ./services/matrix/matrix-sliding-sync.nix
   ./services/matrix/synapse.nix
   ./services/misc/airsonic.nix
+  ./services/misc/amazon-ssm-agent.nix
   ./services/misc/ananicy.nix
   ./services/misc/ankisyncd.nix
   ./services/misc/apache-kafka.nix
@@ -718,6 +731,7 @@
   ./services/misc/ripple-data-api.nix
   ./services/misc/rippled.nix
   ./services/misc/rmfakecloud.nix
+  ./services/misc/rkvm.nix
   ./services/misc/rshim.nix
   ./services/misc/safeeyes.nix
   ./services/misc/sdrplay.nix
@@ -726,11 +740,12 @@
   ./services/misc/signald.nix
   ./services/misc/siproxd.nix
   ./services/misc/snapper.nix
+  ./services/misc/soft-serve.nix
   ./services/misc/sonarr.nix
   ./services/misc/sourcehut
+  ./services/misc/spice-autorandr.nix
   ./services/misc/spice-vdagentd.nix
   ./services/misc/spice-webdavd.nix
-  ./services/misc/ssm-agent.nix
   ./services/misc/sssd.nix
   ./services/misc/subsonic.nix
   ./services/misc/sundtek.nix
@@ -755,12 +770,14 @@
   ./services/monitoring/below.nix
   ./services/monitoring/bosun.nix
   ./services/monitoring/cadvisor.nix
+  ./services/monitoring/certspotter.nix
   ./services/monitoring/cockpit.nix
   ./services/monitoring/collectd.nix
   ./services/monitoring/das_watchdog.nix
   ./services/monitoring/datadog-agent.nix
   ./services/monitoring/do-agent.nix
   ./services/monitoring/fusion-inventory.nix
+  ./services/monitoring/goss.nix
   ./services/monitoring/grafana-agent.nix
   ./services/monitoring/grafana-image-renderer.nix
   ./services/monitoring/grafana-reporter.nix
@@ -772,6 +789,7 @@
   ./services/monitoring/kapacitor.nix
   ./services/monitoring/karma.nix
   ./services/monitoring/kthxbye.nix
+  ./services/monitoring/librenms.nix
   ./services/monitoring/loki.nix
   ./services/monitoring/longview.nix
   ./services/monitoring/mackerel-agent.nix
@@ -878,6 +896,8 @@
   ./services/networking/croc.nix
   ./services/networking/dae.nix
   ./services/networking/dante.nix
+  ./services/networking/deconz.nix
+  ./services/networking/ddclient.nix
   ./services/networking/dhcpcd.nix
   ./services/networking/dnscache.nix
   ./services/networking/dnscrypt-proxy2.nix
@@ -893,6 +913,7 @@
   ./services/networking/eternal-terminal.nix
   ./services/networking/expressvpn.nix
   ./services/networking/fakeroute.nix
+  ./services/networking/fastnetmon-advanced.nix
   ./services/networking/ferm.nix
   ./services/networking/firefox-syncserver.nix
   ./services/networking/fireqos.nix
@@ -982,6 +1003,7 @@
   ./services/networking/ndppd.nix
   ./services/networking/nebula.nix
   ./services/networking/netbird.nix
+  ./services/networking/netclient.nix
   ./services/networking/networkd-dispatcher.nix
   ./services/networking/networkmanager.nix
   ./services/networking/nextdns.nix
@@ -1019,7 +1041,6 @@
   ./services/networking/powerdns.nix
   ./services/networking/pppd.nix
   ./services/networking/pptpd.nix
-  ./services/networking/prayer.nix
   ./services/networking/privoxy.nix
   ./services/networking/prosody.nix
   ./services/networking/quassel.nix
@@ -1032,6 +1053,7 @@
   ./services/networking/redsocks.nix
   ./services/networking/resilio.nix
   ./services/networking/robustirc-bridge.nix
+  ./services/networking/rosenpass.nix
   ./services/networking/routedns.nix
   ./services/networking/rpcbind.nix
   ./services/networking/rxe.nix
@@ -1079,6 +1101,7 @@
   ./services/networking/thelounge.nix
   ./services/networking/tinc.nix
   ./services/networking/tinydns.nix
+  ./services/networking/tinyproxy.nix
   ./services/networking/tmate-ssh-server.nix
   ./services/networking/tox-bootstrapd.nix
   ./services/networking/tox-node.nix
@@ -1124,7 +1147,6 @@
   ./services/search/elasticsearch-curator.nix
   ./services/search/elasticsearch.nix
   ./services/search/hound.nix
-  ./services/search/kibana.nix
   ./services/search/meilisearch.nix
   ./services/search/opensearch.nix
   ./services/search/qdrant.nix
@@ -1145,6 +1167,7 @@
   ./services/security/hologram-agent.nix
   ./services/security/hologram-server.nix
   ./services/security/infnoise.nix
+  ./services/security/jitterentropy-rngd.nix
   ./services/security/kanidm.nix
   ./services/security/munge.nix
   ./services/security/nginx-sso.nix
@@ -1153,12 +1176,12 @@
   ./services/security/opensnitch.nix
   ./services/security/pass-secret-service.nix
   ./services/security/physlock.nix
-  ./services/security/privacyidea.nix
   ./services/security/shibboleth-sp.nix
   ./services/security/sks.nix
   ./services/security/sshguard.nix
   ./services/security/sslmate-agent.nix
   ./services/security/step-ca.nix
+  ./services/security/tang.nix
   ./services/security/tor.nix
   ./services/security/torify.nix
   ./services/security/torsocks.nix
@@ -1209,13 +1232,14 @@
   ./services/web-apps/atlassian/confluence.nix
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
+  ./services/web-apps/audiobookshelf.nix
   ./services/web-apps/bookstack.nix
+  ./services/web-apps/c2fmzq-server.nix
   ./services/web-apps/calibre-web.nix
   ./services/web-apps/coder.nix
   ./services/web-apps/changedetection-io.nix
   ./services/web-apps/chatgpt-retrieval-plugin.nix
   ./services/web-apps/cloudlog.nix
-  ./services/web-apps/code-server.nix
   ./services/web-apps/convos.nix
   ./services/web-apps/dex.nix
   ./services/web-apps/discourse.nix
@@ -1249,6 +1273,7 @@
   ./services/web-apps/kavita.nix
   ./services/web-apps/keycloak.nix
   ./services/web-apps/komga.nix
+  ./services/web-apps/lanraragi.nix
   ./services/web-apps/lemmy.nix
   ./services/web-apps/limesurvey.nix
   ./services/web-apps/mainsail.nix
@@ -1257,6 +1282,7 @@
   ./services/web-apps/mattermost.nix
   ./services/web-apps/mediawiki.nix
   ./services/web-apps/meme-bingo-web.nix
+  ./services/web-apps/microbin.nix
   ./services/web-apps/miniflux.nix
   ./services/web-apps/monica.nix
   ./services/web-apps/moodle.nix
@@ -1282,6 +1308,7 @@
   ./services/web-apps/powerdns-admin.nix
   ./services/web-apps/prosody-filer.nix
   ./services/web-apps/restya-board.nix
+  ./services/web-apps/rimgo.nix
   ./services/web-apps/sftpgo.nix
   ./services/web-apps/rss-bridge.nix
   ./services/web-apps/selfoss.nix
@@ -1298,6 +1325,7 @@
   ./services/web-apps/writefreely.nix
   ./services/web-apps/youtrack.nix
   ./services/web-apps/zabbix.nix
+  ./services/web-apps/zitadel.nix
   ./services/web-servers/agate.nix
   ./services/web-servers/apache-httpd/default.nix
   ./services/web-servers/caddy/default.nix
@@ -1379,6 +1407,7 @@
   ./system/activation/activatable-system.nix
   ./system/activation/activation-script.nix
   ./system/activation/specialisation.nix
+  ./system/activation/switchable-system.nix
   ./system/activation/bootspec.nix
   ./system/activation/top-level.nix
   ./system/boot/binfmt.nix
@@ -1476,6 +1505,7 @@
   ./virtualisation/docker.nix
   ./virtualisation/ecs-agent.nix
   ./virtualisation/hyperv-guest.nix
+  ./virtualisation/incus.nix
   ./virtualisation/kvmgt.nix
   ./virtualisation/libvirtd.nix
   ./virtualisation/lxc.nix
@@ -1500,5 +1530,9 @@
   ./virtualisation/waydroid.nix
   ./virtualisation/xe-guest-utilities.nix
   ./virtualisation/xen-dom0.nix
-  { documentation.nixos.extraModules = [ ./virtualisation/qemu-vm.nix ]; }
+  { documentation.nixos.extraModules = [
+    ./virtualisation/qemu-vm.nix
+    ./image/repart.nix
+    ];
+  }
 ]
diff --git a/nixos/modules/profiles/image-based-appliance.nix b/nixos/modules/profiles/image-based-appliance.nix
new file mode 100644
index 00000000000..7e8b6f696d5
--- /dev/null
+++ b/nixos/modules/profiles/image-based-appliance.nix
@@ -0,0 +1,26 @@
+# This profile sets up a sytem for image based appliance usage. An appliance is
+# installed as an image, cannot be re-built, has no Nix available, and is
+# generally not meant for interactive use. Updates to such an appliance are
+# handled by updating whole partition images via a tool like systemd-sysupdate.
+
+{ lib, modulesPath, ... }:
+
+{
+
+  # Appliances are always "minimal".
+  imports = [
+    "${modulesPath}/profiles/minimal.nix"
+  ];
+
+  # The system cannot be rebuilt.
+  nix.enable = false;
+  system.switch.enable = false;
+
+  # The system is static.
+  users.mutableUsers = false;
+
+  # The system avoids interpreters as much as possible to reduce its attack
+  # surface.
+  boot.initrd.systemd.enable = lib.mkDefault true;
+  networking.useNetworkd = lib.mkDefault true;
+}
diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix
index 19e7eb32e83..52750cd472d 100644
--- a/nixos/modules/profiles/installation-device.nix
+++ b/nixos/modules/profiles/installation-device.nix
@@ -102,8 +102,6 @@ with lib;
         jq # for closureInfo
         # For boot.initrd.systemd
         makeInitrdNGTool
-        systemdStage1
-        systemdStage1Network
       ];
 
     boot.swraid.enable = true;
diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix
index cc01b16960c..d48afed18f7 100644
--- a/nixos/modules/profiles/macos-builder.nix
+++ b/nixos/modules/profiles/macos-builder.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, options, ... }:
 
 let
   keysDirectory = "/var/keys";
@@ -163,9 +163,15 @@ in
 
       in
       script.overrideAttrs (old: {
+        pos = __curPos; # sets meta.position to point here; see script binding above for package definition
         meta = (old.meta or { }) // {
           platforms = lib.platforms.darwin;
         };
+        passthru = (old.passthru or { }) // {
+          # Let users in the repl inspect the config
+          nixosConfig = config;
+          nixosOptions = options;
+        };
       });
 
     system = {
diff --git a/nixos/modules/profiles/minimal.nix b/nixos/modules/profiles/minimal.nix
index bd1b2b45218..75f355b4a00 100644
--- a/nixos/modules/profiles/minimal.nix
+++ b/nixos/modules/profiles/minimal.nix
@@ -18,6 +18,15 @@ with lib;
 
   documentation.nixos.enable = mkDefault false;
 
+  # Perl is a default package.
+  environment.defaultPackages = mkDefault [ ];
+
+  # The lessopen package pulls in Perl.
+  programs.less.lessopen = mkDefault null;
+
+  # This pulls in nixos-containers which depends on Perl.
+  boot.enableContainers = mkDefault false;
+
   programs.command-not-found.enable = mkDefault false;
 
   services.logrotate.enable = mkDefault false;
diff --git a/nixos/modules/programs/ausweisapp.nix b/nixos/modules/programs/ausweisapp.nix
index ef1f059568c..91870df2024 100644
--- a/nixos/modules/programs/ausweisapp.nix
+++ b/nixos/modules/programs/ausweisapp.nix
@@ -7,11 +7,11 @@ let
 in
 {
   options.programs.ausweisapp = {
-    enable = mkEnableOption (lib.mdDoc "AusweisApp2");
+    enable = mkEnableOption (lib.mdDoc "AusweisApp");
 
     openFirewall = mkOption {
       description = lib.mdDoc ''
-        Whether to open the required firewall ports for the Smartphone as Card Reader (SaC) functionality of AusweisApp2.
+        Whether to open the required firewall ports for the Smartphone as Card Reader (SaC) functionality of AusweisApp.
       '';
       default = false;
       type = lib.types.bool;
@@ -19,7 +19,7 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = with pkgs; [ AusweisApp2 ];
+    environment.systemPackages = with pkgs; [ ausweisapp ];
     networking.firewall.allowedUDPPorts = lib.optionals cfg.openFirewall [ 24727 ];
   };
 }
diff --git a/nixos/modules/programs/bandwhich.nix b/nixos/modules/programs/bandwhich.nix
index 8d1612217ad..aa6a0dfb6ff 100644
--- a/nixos/modules/programs/bandwhich.nix
+++ b/nixos/modules/programs/bandwhich.nix
@@ -24,7 +24,7 @@ in {
     security.wrappers.bandwhich = {
       owner = "root";
       group = "root";
-      capabilities = "cap_net_raw,cap_net_admin+ep";
+      capabilities = "cap_sys_ptrace,cap_dac_read_search,cap_net_raw,cap_net_admin+ep";
       source = "${pkgs.bandwhich}/bin/bandwhich";
     };
   };
diff --git a/nixos/modules/programs/browserpass.nix b/nixos/modules/programs/browserpass.nix
index 346d38e5e88..a9670a37e61 100644
--- a/nixos/modules/programs/browserpass.nix
+++ b/nixos/modules/programs/browserpass.nix
@@ -27,6 +27,6 @@ with lib;
       "opt/brave/native-messaging-hosts/${appId}".source = source "hosts/chromium";
       "opt/brave/policies/managed/${appId}".source = source "policies/chromium";
     };
-    nixpkgs.config.firefox.enableBrowserpass = true;
+    programs.firefox.nativeMessagingHosts.packages = [ pkgs.browserpass ];
   };
 }
diff --git a/nixos/modules/programs/calls.nix b/nixos/modules/programs/calls.nix
index 7a18982915a..3d757bc1fc3 100644
--- a/nixos/modules/programs/calls.nix
+++ b/nixos/modules/programs/calls.nix
@@ -8,7 +8,7 @@ in {
   options = {
     programs.calls = {
       enable = mkEnableOption (lib.mdDoc ''
-        Whether to enable GNOME calls: a phone dialer and call handler.
+        GNOME calls: a phone dialer and call handler
       '');
     };
   };
diff --git a/nixos/modules/programs/cdemu.nix b/nixos/modules/programs/cdemu.nix
index d43f009f2f9..7eba4d29d83 100644
--- a/nixos/modules/programs/cdemu.nix
+++ b/nixos/modules/programs/cdemu.nix
@@ -53,6 +53,19 @@ in {
       dbus.packages = [ pkgs.cdemu-daemon ];
     };
 
+    users.groups.${config.programs.cdemu.group} = {};
+
+    # Systemd User service
+    # manually adapted from example in source package:
+    # https://sourceforge.net/p/cdemu/code/ci/master/tree/cdemu-daemon/service-example/cdemu-daemon.service
+    systemd.user.services.cdemu-daemon.description = "CDEmu daemon";
+    systemd.user.services.cdemu-daemon.serviceConfig = {
+      Type = "dbus";
+      BusName = "net.sf.cdemu.CDEmuDaemon";
+      ExecStart = "${pkgs.cdemu-daemon}/bin/cdemu-daemon --config-file \"%h/.config/cdemu-daemon\"";
+      Restart = "no";
+    };
+
     environment.systemPackages =
       [ pkgs.cdemu-daemon pkgs.cdemu-client ]
       ++ optional cfg.gui pkgs.gcdemu
diff --git a/nixos/modules/programs/cnping.nix b/nixos/modules/programs/cnping.nix
index d3cf659d429..143267fc9a4 100644
--- a/nixos/modules/programs/cnping.nix
+++ b/nixos/modules/programs/cnping.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     programs.cnping = {
-      enable = mkEnableOption (lib.mdDoc "Whether to install a setcap wrapper for cnping");
+      enable = mkEnableOption (lib.mdDoc "a setcap wrapper for cnping");
     };
   };
 
diff --git a/nixos/modules/programs/direnv.nix b/nixos/modules/programs/direnv.nix
index 1a80cb20280..2566fa7699b 100644
--- a/nixos/modules/programs/direnv.nix
+++ b/nixos/modules/programs/direnv.nix
@@ -11,7 +11,7 @@ in {
     enable = lib.mkEnableOption (lib.mdDoc ''
       direnv integration. Takes care of both installation and
       setting up the sourcing of the shell. Additionally enables nix-direnv
-      integration. Note that you need to logout and login for this change to apply.
+      integration. Note that you need to logout and login for this change to apply
     '');
 
     package = lib.mkPackageOptionMD pkgs "direnv" {};
@@ -54,7 +54,7 @@ in {
   };
 
   imports = [
-    (lib.mkRemovedOptionModule ["programs" "direnv" "persistDerivations"] "persistDerivations was removed as it is on longer necessary")
+    (lib.mkRemovedOptionModule ["programs" "direnv" "persistDerivations"] "persistDerivations was removed as it is no longer necessary")
   ];
 
   config = lib.mkIf cfg.enable {
diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix
index 324b1918474..6cf9257d035 100644
--- a/nixos/modules/programs/environment.nix
+++ b/nixos/modules/programs/environment.nix
@@ -22,7 +22,6 @@ in
         # be specified here; do so in the default value of programs.less.envVariables instead
         PAGER = mkDefault "less";
         EDITOR = mkDefault "nano";
-        XDG_CONFIG_DIRS = [ "/etc/xdg" ]; # needs to be before profile-relative paths to allow changes through environment.etc
       };
 
     # since we set PAGER to this above, make sure it's installed
@@ -33,6 +32,11 @@ in
         "/run/current-system/sw"
       ];
 
+    environment.sessionVariables =
+      {
+        XDG_CONFIG_DIRS = [ "/etc/xdg" ]; # needs to be before profile-relative paths to allow changes through environment.etc
+      };
+
     # TODO: move most of these elsewhere
     environment.profileRelativeSessionVariables =
       { PATH = [ "/bin" ];
diff --git a/nixos/modules/programs/feedbackd.nix b/nixos/modules/programs/feedbackd.nix
index cee8daa3146..e3fde947a3d 100644
--- a/nixos/modules/programs/feedbackd.nix
+++ b/nixos/modules/programs/feedbackd.nix
@@ -8,9 +8,9 @@ in {
   options = {
     programs.feedbackd = {
       enable = mkEnableOption (lib.mdDoc ''
-        Whether to enable the feedbackd D-BUS service and udev rules.
+        the feedbackd D-BUS service and udev rules.
 
-        Your user needs to be in the `feedbackd` group to trigger effects.
+        Your user needs to be in the `feedbackd` group to trigger effects
       '');
       package = mkOption {
         description = lib.mdDoc ''
diff --git a/nixos/modules/programs/firefox.nix b/nixos/modules/programs/firefox.nix
index 8653f066cf8..1edf935d164 100644
--- a/nixos/modules/programs/firefox.nix
+++ b/nixos/modules/programs/firefox.nix
@@ -5,8 +5,6 @@ with lib;
 let
   cfg = config.programs.firefox;
 
-  nmh = cfg.nativeMessagingHosts;
-
   policyFormat = pkgs.formats.json { };
 
   organisationInfo = ''
@@ -17,6 +15,50 @@ let
     given control of your browser, unless of course they also control your
     NixOS configuration.
   '';
+
+  # deprecated per-native-messaging-host options
+  nmhOptions = {
+    browserpass = {
+      name = "Browserpass";
+      package = pkgs.browserpass;
+    };
+    bukubrow = {
+      name = "Bukubrow";
+      package = pkgs.bukubrow;
+    };
+    euwebid = {
+      name = "Web eID";
+      package = pkgs.web-eid-app;
+    };
+    ff2mpv = {
+      name = "ff2mpv";
+      package = pkgs.ff2mpv;
+    };
+    fxCast = {
+      name = "fx_cast";
+      package = pkgs.fx-cast-bridge;
+    };
+    gsconnect = {
+      name = "GSConnect";
+      package = pkgs.gnomeExtensions.gsconnect;
+    };
+    jabref = {
+      name = "JabRef";
+      package = pkgs.jabref;
+    };
+    passff = {
+      name = "PassFF";
+      package = pkgs.passff-host;
+    };
+    tridactyl = {
+      name = "Tridactyl";
+      package = pkgs.tridactyl-native;
+    };
+    ugetIntegrator = {
+      name = "Uget Integrator";
+      package = pkgs.uget-integrator;
+    };
+  };
 in
 {
   options.programs.firefox = {
@@ -36,13 +78,19 @@ in
       ];
     };
 
+    wrapperConfig = mkOption {
+      type = types.attrs;
+      default = {};
+      description = mdDoc "Arguments to pass to Firefox wrapper";
+    };
+
     policies = mkOption {
       type = policyFormat.type;
       default = { };
       description = mdDoc ''
         Group policies to install.
 
-        See [Mozilla's documentation](https://github.com/mozilla/policy-templates/blob/master/README.md)
+        See [Mozilla's documentation](https://mozilla.github.io/policy-templates/)
         for a list of available options.
 
         This can be used to install extensions declaratively! Check out the
@@ -198,46 +246,34 @@ in
       '';
     };
 
-    nativeMessagingHosts = mapAttrs (_: v: mkEnableOption (mdDoc v)) {
-      browserpass = "Browserpass support";
-      bukubrow = "Bukubrow support";
-      euwebid = "Web eID support";
-      ff2mpv = "ff2mpv support";
-      fxCast = "fx_cast support";
-      gsconnect = "GSConnect support";
-      jabref = "JabRef support";
-      passff = "PassFF support";
-      tridactyl = "Tridactyl support";
-      ugetIntegrator = "Uget Integrator support";
-    };
+    nativeMessagingHosts = ({
+      packages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = mdDoc ''
+          Additional packages containing native messaging hosts that should be made available to Firefox extensions.
+        '';
+      };
+    }) // (mapAttrs (k: v: mkEnableOption (mdDoc "${v.name} support")) nmhOptions);
   };
 
-  config = mkIf cfg.enable {
+  config = let
+    forEachEnabledNmh = fn: flatten (mapAttrsToList (k: v: lib.optional cfg.nativeMessagingHosts.${k} (fn k v)) nmhOptions);
+  in mkIf cfg.enable {
+    warnings = forEachEnabledNmh (k: v:
+      "The `programs.firefox.nativeMessagingHosts.${k}` option is deprecated, " +
+      "please add `${v.package.pname}` to `programs.firefox.nativeMessagingHosts.packages` instead."
+    );
+    programs.firefox.nativeMessagingHosts.packages = forEachEnabledNmh (_: v: v.package);
+
     environment.systemPackages = [
-      (cfg.package.override {
-        extraPrefs = cfg.autoConfig;
-        extraNativeMessagingHosts = with pkgs; optionals nmh.ff2mpv [
-          ff2mpv
-        ] ++ optionals nmh.euwebid [
-          web-eid-app
-        ] ++ optionals nmh.gsconnect [
-          gnomeExtensions.gsconnect
-        ] ++ optionals nmh.jabref [
-          jabref
-        ] ++ optionals nmh.passff [
-          passff-host
-        ];
-      })
+      (cfg.package.override (old: {
+        extraPrefsFiles = old.extraPrefsFiles or [] ++ [(pkgs.writeText "firefox-autoconfig.js" cfg.autoConfig)];
+        nativeMessagingHosts = old.nativeMessagingHosts or [] ++ cfg.nativeMessagingHosts.packages;
+        cfg = (old.cfg or {}) // cfg.wrapperConfig;
+      }))
     ];
 
-    nixpkgs.config.firefox = {
-      enableBrowserpass = nmh.browserpass;
-      enableBukubrow = nmh.bukubrow;
-      enableTridactylNative = nmh.tridactyl;
-      enableUgetIntegrator = nmh.ugetIntegrator;
-      enableFXCastBridge = nmh.fxCast;
-    };
-
     environment.etc =
       let
         policiesJSON = policyFormat.generate "firefox-policies.json" { inherit (cfg) policies; };
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index b500b8f24b2..a4c20560bc9 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -208,7 +208,7 @@ in
         end
 
         # if we haven't sourced the login config, do it
-        status --is-login; and not set -q __fish_nixos_login_config_sourced
+        status is-login; and not set -q __fish_nixos_login_config_sourced
         and begin
           ${sourceEnv "loginShellInit"}
 
@@ -220,7 +220,7 @@ in
         end
 
         # if we haven't sourced the interactive config, do it
-        status --is-interactive; and not set -q __fish_nixos_interactive_config_sourced
+        status is-interactive; and not set -q __fish_nixos_interactive_config_sourced
         and begin
           ${fishAbbrs}
           ${fishAliases}
@@ -268,7 +268,7 @@ in
             ''
               mkdir -p $out
               if [ -d $package/share/man ]; then
-                find $package/share/man -type f | xargs ${pkgs.python3.pythonForBuild.interpreter} ${patchedGenerator}/create_manpage_completions.py --directory $out >/dev/null
+                find $package/share/man -type f | xargs ${pkgs.python3.pythonOnBuildForHost.interpreter} ${patchedGenerator}/create_manpage_completions.py --directory $out >/dev/null
               fi
             '';
         in
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index 697b6e9a0bd..aa1a536247c 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -6,6 +6,10 @@ let
 
   cfg = config.programs.gnupg;
 
+  agentSettingsFormat = pkgs.formats.keyValue {
+    mkKeyValue = lib.generators.mkKeyValueDefault { } " ";
+  };
+
   xserverCfg = config.services.xserver;
 
   defaultPinentryFlavor =
@@ -82,6 +86,18 @@ in
       '';
     };
 
+    agent.settings = mkOption {
+      type = agentSettingsFormat.type;
+      default = { };
+      example = {
+        default-cache-ttl = 600;
+      };
+      description = lib.mdDoc ''
+        Configuration for /etc/gnupg/gpg-agent.conf.
+        See {manpage}`gpg-agent(1)` for supported options.
+      '';
+    };
+
     dirmngr.enable = mkOption {
       type = types.bool;
       default = false;
@@ -92,17 +108,20 @@ in
   };
 
   config = mkIf cfg.agent.enable {
-    environment.etc."gnupg/gpg-agent.conf".text =
-      lib.optionalString (cfg.agent.pinentryFlavor != null) ''
-      pinentry-program ${pkgs.pinentry.${cfg.agent.pinentryFlavor}}/bin/pinentry
-    '';
+    programs.gnupg.agent.settings = {
+      pinentry-program = lib.mkIf (cfg.agent.pinentryFlavor != null)
+        "${pkgs.pinentry.${cfg.agent.pinentryFlavor}}/bin/pinentry";
+    };
+
+    environment.etc."gnupg/gpg-agent.conf".source =
+      agentSettingsFormat.generate "gpg-agent.conf" cfg.agent.settings;
 
     # This overrides the systemd user unit shipped with the gnupg package
     systemd.user.services.gpg-agent = {
       unitConfig = {
         Description = "GnuPG cryptographic agent and passphrase cache";
         Documentation = "man:gpg-agent(1)";
-        Requires = [ "gpg-agent.socket" ];
+        Requires = [ "sockets.target" ];
       };
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/gpg-agent --supervised";
diff --git a/nixos/modules/programs/kdeconnect.nix b/nixos/modules/programs/kdeconnect.nix
index 4978c428ce3..4ba156f2db8 100644
--- a/nixos/modules/programs/kdeconnect.nix
+++ b/nixos/modules/programs/kdeconnect.nix
@@ -9,7 +9,7 @@ with lib;
       1714 to 1764 as they are needed for it to function properly.
       You can use the {option}`package` to use
       `gnomeExtensions.gsconnect` as an alternative
-      implementation if you use Gnome.
+      implementation if you use Gnome
     '');
     package = mkOption {
       default = pkgs.plasma5Packages.kdeconnect-kde;
diff --git a/nixos/modules/programs/nano.nix b/nixos/modules/programs/nano.nix
index 7705bf0ddc7..88404f3557c 100644
--- a/nixos/modules/programs/nano.nix
+++ b/nixos/modules/programs/nano.nix
@@ -2,14 +2,16 @@
 
 let
   cfg = config.programs.nano;
-  LF = "\n";
 in
 
 {
-  ###### interface
-
   options = {
     programs.nano = {
+      enable = lib.mkEnableOption (lib.mdDoc "nano") // {
+        default = true;
+      };
+
+      package = lib.mkPackageOptionMD pkgs "nano" { };
 
       nanorc = lib.mkOption {
         type = lib.types.lines;
@@ -24,6 +26,7 @@ in
           set tabsize 2
         '';
       };
+
       syntaxHighlight = lib.mkOption {
         type = lib.types.bool;
         default = true;
@@ -32,20 +35,14 @@ in
     };
   };
 
-  ###### implementation
-
-  config = lib.mkIf (cfg.nanorc != "" || cfg.syntaxHighlight) {
-    environment.etc.nanorc.text = lib.concatStringsSep LF (
-      ( lib.optionals cfg.syntaxHighlight [
-          "# The line below is added because value of programs.nano.syntaxHighlight is set to true"
-          ''include "${pkgs.nano}/share/nano/*.nanorc"''
-          ""
-      ])
-      ++ ( lib.optionals (cfg.nanorc != "") [
-        "# The lines below have been set from value of programs.nano.nanorc"
-        cfg.nanorc
-      ])
-    );
+  config = lib.mkIf cfg.enable {
+    environment = {
+      etc.nanorc.text = (lib.optionalString cfg.syntaxHighlight ''
+        # load syntax highlighting files
+        include "${cfg.package}/share/nano/*.nanorc"
+        include "${cfg.package}/share/nano/extra/*.nanorc"
+      '') + cfg.nanorc;
+      systemPackages = [ cfg.package ];
+    };
   };
-
 }
diff --git a/nixos/modules/programs/npm.nix b/nixos/modules/programs/npm.nix
index 48dc48e668f..c41fea32614 100644
--- a/nixos/modules/programs/npm.nix
+++ b/nixos/modules/programs/npm.nix
@@ -34,7 +34,7 @@ in
           prefix = ''${HOME}/.npm
           https-proxy=proxy.example.com
           init-license=MIT
-          init-author-url=http://npmjs.org
+          init-author-url=https://www.npmjs.com/
           color=true
         '';
       };
diff --git a/nixos/modules/programs/openvpn3.nix b/nixos/modules/programs/openvpn3.nix
index df7e9ef22c1..37a1bfeb0c3 100644
--- a/nixos/modules/programs/openvpn3.nix
+++ b/nixos/modules/programs/openvpn3.nix
@@ -8,11 +8,23 @@ in
 {
   options.programs.openvpn3 = {
     enable = mkEnableOption (lib.mdDoc "the openvpn3 client");
+    package = mkOption {
+      type = types.package;
+      default = pkgs.openvpn3.override {
+        enableSystemdResolved = config.services.resolved.enable;
+      };
+      defaultText = literalExpression ''pkgs.openvpn3.override {
+        enableSystemdResolved = config.services.resolved.enable;
+      }'';
+      description = lib.mdDoc ''
+        Which package to use for `openvpn3`.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
-    services.dbus.packages = with pkgs; [
-      openvpn3
+    services.dbus.packages = [
+      cfg.package
     ];
 
     users.users.openvpn = {
@@ -25,8 +37,8 @@ in
       gid = config.ids.gids.openvpn;
     };
 
-    environment.systemPackages = with pkgs; [
-      openvpn3
+    environment.systemPackages = [
+      cfg.package
     ];
   };
 
diff --git a/nixos/modules/programs/projecteur.nix b/nixos/modules/programs/projecteur.nix
new file mode 100644
index 00000000000..9fcd357d3b2
--- /dev/null
+++ b/nixos/modules/programs/projecteur.nix
@@ -0,0 +1,20 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.projecteur;
+in
+{
+  options.programs.projecteur = {
+    enable = lib.mkEnableOption (lib.mdDoc "projecteur");
+    package = lib.mkPackageOptionMD pkgs "projecteur" { };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ benneti drupol ];
+  };
+}
diff --git a/nixos/modules/programs/regreet.nix b/nixos/modules/programs/regreet.nix
index f6c750a45bf..0fd9cf23298 100644
--- a/nixos/modules/programs/regreet.nix
+++ b/nixos/modules/programs/regreet.nix
@@ -36,6 +36,19 @@ in
       '';
     };
 
+    cageArgs = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ "-s" ];
+      example = lib.literalExpression
+        ''
+          [ "-s" "-m" "last" ]
+        '';
+      description = lib.mdDoc ''
+        Additional arguments to be passed to
+        [cage](https://github.com/cage-kiosk/cage).
+      '';
+    };
+
     extraCss = lib.mkOption {
       type = lib.types.either lib.types.path lib.types.lines;
       default = "";
@@ -50,7 +63,7 @@ in
   config = lib.mkIf cfg.enable {
     services.greetd = {
       enable = lib.mkDefault true;
-      settings.default_session.command = lib.mkDefault "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -- ${lib.getExe cfg.package}";
+      settings.default_session.command = lib.mkDefault "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package}";
     };
 
     environment.etc = {
@@ -66,10 +79,10 @@ in
     };
 
     systemd.tmpfiles.rules = let
-      user = config.services.greetd.settings.default_session.user;
+      group = config.users.users.${config.services.greetd.settings.default_session.user}.group;
     in [
-      "d /var/log/regreet 0755 greeter ${user} - -"
-      "d /var/cache/regreet 0755 greeter ${user} - -"
+      "d /var/log/regreet 0755 greeter ${group} - -"
+      "d /var/cache/regreet 0755 greeter ${group} - -"
     ];
   };
 }
diff --git a/nixos/modules/programs/rust-motd.nix b/nixos/modules/programs/rust-motd.nix
index d5f1820ba75..4c9b1018596 100644
--- a/nixos/modules/programs/rust-motd.nix
+++ b/nixos/modules/programs/rust-motd.nix
@@ -5,6 +5,23 @@ with lib;
 let
   cfg = config.programs.rust-motd;
   format = pkgs.formats.toml { };
+
+  # Order the sections in the TOML according to the order of sections
+  # in `cfg.order`.
+  motdConf = pkgs.runCommand "motd.conf"
+    {
+      __structuredAttrs = true;
+      inherit (cfg) order settings;
+      nativeBuildInputs = [ pkgs.remarshal pkgs.jq ];
+    }
+    ''
+      cat "$NIX_ATTRS_JSON_FILE" \
+        | jq '.settings as $settings
+              | .order
+              | map({ key: ., value: $settings."\(.)" })
+              | from_entries' -r \
+        | json2toml /dev/stdin "$out"
+    '';
 in {
   options.programs.rust-motd = {
     enable = mkEnableOption (lib.mdDoc "rust-motd");
@@ -27,10 +44,43 @@ in {
         For possible formats, please refer to {manpage}`systemd.time(7)`.
       '';
     };
+    order = mkOption {
+      type = types.listOf types.str;
+      default = attrNames cfg.settings;
+      defaultText = literalExpression "attrNames cfg.settings";
+      description = mdDoc ''
+        The order of the sections in [](#opt-programs.rust-motd.settings).
+        By default they are ordered alphabetically.
+
+        Context: since attribute sets in Nix are always
+        ordered alphabetically internally this means that
+
+        ```nix
+        {
+          uptime = { /* ... */ };
+          banner = { /* ... */ };
+        }
+        ```
+
+        will still have `banner` displayed before `uptime`.
+
+        To work around that, this option can be used to define the order of all keys,
+        i.e.
+
+        ```nix
+        {
+          order = [
+            "uptime"
+            "banner"
+          ];
+        }
+        ```
+
+        makes sure that `uptime` is placed before `banner` in the motd.
+      '';
+    };
     settings = mkOption {
-      type = types.submodule {
-        freeformType = format.type;
-      };
+      type = types.attrsOf format.type;
       description = mdDoc ''
         Settings on what to generate. Please read the
         [upstream documentation](https://github.com/rust-motd/rust-motd/blob/main/README.md#configuration)
@@ -45,14 +95,21 @@ in {
           `programs.rust-motd` is incompatible with `users.motd`!
         '';
       }
+      { assertion = sort (a: b: a < b) cfg.order == attrNames cfg.settings;
+        message = ''
+          Please ensure that every section from `programs.rust-motd.settings` is present in
+          `programs.rust-motd.order`.
+        '';
+      }
     ];
     systemd.services.rust-motd = {
       path = with pkgs; [ bash ];
       documentation = [ "https://github.com/rust-motd/rust-motd/blob/v${pkgs.rust-motd.version}/README.md" ];
       description = "motd generator";
+      wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         ExecStart = "${pkgs.writeShellScript "update-motd" ''
-          ${pkgs.rust-motd}/bin/rust-motd ${format.generate "motd.conf" cfg.settings} > motd
+          ${pkgs.rust-motd}/bin/rust-motd ${motdConf} > motd
         ''}";
         CapabilityBoundingSet = [ "" ];
         LockPersonality = true;
diff --git a/nixos/modules/programs/virt-manager.nix b/nixos/modules/programs/virt-manager.nix
new file mode 100644
index 00000000000..095db7586a0
--- /dev/null
+++ b/nixos/modules/programs/virt-manager.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.virt-manager;
+in {
+  options.programs.virt-manager = {
+    enable = lib.mkEnableOption "virt-manager, an UI for managing virtual machines in libvirt";
+
+    package = lib.mkPackageOption pkgs "virt-manager" {};
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    programs.dconf.enable = true;
+  };
+}
diff --git a/nixos/modules/programs/wayland/cardboard.nix b/nixos/modules/programs/wayland/cardboard.nix
new file mode 100644
index 00000000000..262c698c74b
--- /dev/null
+++ b/nixos/modules/programs/wayland/cardboard.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.cardboard;
+in
+{
+  meta.maintainers = with lib.maintainers; [ AndersonTorres ];
+
+  options.programs.cardboard = {
+    enable = lib.mkEnableOption (lib.mdDoc "cardboard");
+
+    package = lib.mkPackageOptionMD pkgs "cardboard" { };
+  };
+
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    {
+      environment.systemPackages = [ cfg.package ];
+
+      # To make a cardboard session available for certain DMs like SDDM
+      services.xserver.displayManager.sessionPackages = [ cfg.package ];
+    }
+    (import ./wayland-session.nix { inherit lib pkgs; })
+  ]);
+}
diff --git a/nixos/modules/programs/wayland/wayfire.nix b/nixos/modules/programs/wayland/wayfire.nix
index d0b280e3940..9ea2010cf59 100644
--- a/nixos/modules/programs/wayland/wayfire.nix
+++ b/nixos/modules/programs/wayland/wayfire.nix
@@ -6,7 +6,7 @@ in
   meta.maintainers = with lib.maintainers; [ rewine ];
 
   options.programs.wayfire = {
-    enable = lib.mkEnableOption (lib.mdDoc "Wayfire, a wayland compositor based on wlroots.");
+    enable = lib.mkEnableOption (lib.mdDoc "Wayfire, a wayland compositor based on wlroots");
 
     package = lib.mkPackageOptionMD pkgs "wayfire" { };
 
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.md b/nixos/modules/programs/zsh/oh-my-zsh.md
index 73d425244ce..6a310006edb 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.md
+++ b/nixos/modules/programs/zsh/oh-my-zsh.md
@@ -78,7 +78,7 @@ If third-party customizations (e.g. new themes) are supposed to be added to
 
   - Completion scripts are supposed to be stored at
     `$out/share/zsh/site-functions`. This directory is part of the
-    [`fpath`](http://zsh.sourceforge.net/Doc/Release/Functions.html)
+    [`fpath`](https://zsh.sourceforge.io/Doc/Release/Functions.html)
     and the package should be compatible with pure `ZSH`
     setups. The module will automatically link the contents of
     `site-functions` to completions directory in the proper
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 408c515044c..3fab863adb7 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -54,7 +54,6 @@ in
     (mkRemovedOptionModule [ "services" "chronos" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "couchpotato" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "dd-agent" ] "dd-agent was removed from nixpkgs in favor of the newer datadog-agent.")
-    (mkRemovedOptionModule [ "services" "ddclient" ] "ddclient has been removed on the request of the upstream maintainer because it is unmaintained and has bugs. Please switch to a different software like `inadyn` or `knsupdate`.") # Added 2023-07-04
     (mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead")
     (mkRemovedOptionModule [ "services" "exhibitor" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" ] "The corresponding package was removed from nixpkgs.")
@@ -112,6 +111,7 @@ in
     (mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "rtsp-simple-server" ] "Package has been completely rebranded by upstream as mediamtx, and thus the service and the package were renamed in NixOS as well.")
+    (mkRemovedOptionModule [ "services" "prayer" ] "The corresponding package was removed from nixpkgs.")
 
     (mkRemovedOptionModule [ "i18n" "inputMethod" "fcitx" ] "The fcitx module has been removed. Please use fcitx5 instead")
     (mkRemovedOptionModule [ "services" "dhcpd4" ] ''
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix
index 92bed172f45..7cc302969fb 100644
--- a/nixos/modules/security/acme/default.nix
+++ b/nixos/modules/security/acme/default.nix
@@ -184,6 +184,7 @@ let
   certToConfig = cert: data: let
     acmeServer = data.server;
     useDns = data.dnsProvider != null;
+    useDnsOrS3 = useDns || data.s3Bucket != null;
     destPath = "/var/lib/acme/${cert}";
     selfsignedDeps = optionals (cfg.preliminarySelfsigned) [ "acme-selfsigned-${cert}.service" ];
 
@@ -219,7 +220,8 @@ let
       [ "--dns" data.dnsProvider ]
       ++ optionals (!data.dnsPropagationCheck) [ "--dns.disable-cp" ]
       ++ optionals (data.dnsResolver != null) [ "--dns.resolvers" data.dnsResolver ]
-    ) else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ]
+    ) else if data.s3Bucket != null then [ "--http" "--http.s3-bucket" data.s3Bucket ]
+    else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ]
     else [ "--http" "--http.webroot" data.webroot ];
 
     commonOpts = [
@@ -343,6 +345,10 @@ let
       serviceConfig = commonServiceConfig // {
         Group = data.group;
 
+        # Let's Encrypt Failed Validation Limit allows 5 retries per hour, per account, hostname and hour.
+        # This avoids eating them all up if something is misconfigured upon the first try.
+        RestartSec = 15 * 60;
+
         # Keep in mind that these directories will be deleted if the user runs
         # systemctl clean --what=state
         # acme/.lego/${cert} is listed for this reason.
@@ -362,13 +368,12 @@ let
           "/var/lib/acme/.lego/${cert}/${certDir}:/tmp/certificates"
         ];
 
-        # Only try loading the environmentFile if the dns challenge is enabled
-        EnvironmentFile = mkIf useDns data.environmentFile;
+        EnvironmentFile = mkIf useDnsOrS3 data.environmentFile;
 
-        Environment = mkIf useDns
+        Environment = mkIf useDnsOrS3
           (mapAttrsToList (k: v: ''"${k}=%d/${k}"'') data.credentialFiles);
 
-        LoadCredential = mkIf useDns
+        LoadCredential = mkIf useDnsOrS3
           (mapAttrsToList (k: v: "${k}:${v}") data.credentialFiles);
 
         # Run as root (Prefixed with +)
@@ -592,7 +597,7 @@ let
         description = lib.mdDoc ''
           Key type to use for private keys.
           For an up to date list of supported values check the --key-type option
-          at <https://go-acme.github.io/lego/usage/cli/#usage>.
+          at <https://go-acme.github.io/lego/usage/cli/options/>.
         '';
       };
 
@@ -755,6 +760,15 @@ let
         '';
       };
 
+      s3Bucket = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "acme";
+        description = lib.mdDoc ''
+          S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket.
+        '';
+      };
+
       inheritDefaults = mkOption {
         default = true;
         example = true;
@@ -928,35 +942,20 @@ in {
             and remove the wildcard from the path.
           '';
         }
-        {
-          assertion = data.dnsProvider == null || data.webroot == null;
+        (let exclusiveAttrs = {
+          inherit (data) dnsProvider webroot listenHTTP s3Bucket;
+        }; in {
+          assertion = lib.length (lib.filter (x: x != null) (builtins.attrValues exclusiveAttrs)) == 1;
           message = ''
-            Options `security.acme.certs.${cert}.dnsProvider` and
-            `security.acme.certs.${cert}.webroot` are mutually exclusive.
+            Exactly one of the options
+            `security.acme.certs.${cert}.dnsProvider`,
+            `security.acme.certs.${cert}.webroot`,
+            `security.acme.certs.${cert}.listenHTTP` and
+            `security.acme.certs.${cert}.s3Bucket`
+            is required.
+            Current values: ${(lib.generators.toPretty {} exclusiveAttrs)}.
           '';
-        }
-        {
-          assertion = data.webroot == null || data.listenHTTP == null;
-          message = ''
-            Options `security.acme.certs.${cert}.webroot` and
-            `security.acme.certs.${cert}.listenHTTP` are mutually exclusive.
-          '';
-        }
-        {
-          assertion = data.listenHTTP == null || data.dnsProvider == null;
-          message = ''
-            Options `security.acme.certs.${cert}.listenHTTP` and
-            `security.acme.certs.${cert}.dnsProvider` are mutually exclusive.
-          '';
-        }
-        {
-          assertion = data.dnsProvider != null || data.webroot != null || data.listenHTTP != null;
-          message = ''
-            One of `security.acme.certs.${cert}.dnsProvider`,
-            `security.acme.certs.${cert}.webroot`, or
-            `security.acme.certs.${cert}.listenHTTP` must be provided.
-          '';
-        }
+        })
         {
           assertion = all (hasSuffix "_FILE") (attrNames data.credentialFiles);
           message = ''
diff --git a/nixos/modules/security/apparmor/profiles.nix b/nixos/modules/security/apparmor/profiles.nix
index 8eb630b5a48..0bf90a00865 100644
--- a/nixos/modules/security/apparmor/profiles.nix
+++ b/nixos/modules/security/apparmor/profiles.nix
@@ -2,10 +2,4 @@
 let apparmor = config.security.apparmor; in
 {
 config.security.apparmor.packages = [ pkgs.apparmor-profiles ];
-config.security.apparmor.policies."bin.ping".profile = lib.mkIf apparmor.policies."bin.ping".enable ''
-  include "${pkgs.iputils.apparmor}/bin.ping"
-  include "${pkgs.inetutils.apparmor}/bin.ping"
-  # Note that including those two profiles in the same profile
-  # would not work if the second one were to re-include <tunables/global>.
-'';
 }
diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix
index 02b11766b3c..2a855a77e3a 100644
--- a/nixos/modules/security/duosec.nix
+++ b/nixos/modules/security/duosec.nix
@@ -193,8 +193,11 @@ in
         source = "${pkgs.duo-unix.out}/bin/login_duo";
       };
 
-    system.activationScripts = {
-      login_duo = mkIf cfg.ssh.enable ''
+    systemd.services.login-duo = lib.mkIf cfg.ssh.enable {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" ];
+      unitConfig.DefaultDependencies = false;
+      script = ''
         if test -f "${cfg.secretKeyFile}"; then
           mkdir -m 0755 -p /etc/duo
 
@@ -209,7 +212,13 @@ in
           mv -fT "$conf" /etc/duo/login_duo.conf
         fi
       '';
-      pam_duo = mkIf cfg.pam.enable ''
+    };
+
+    systemd.services.pam-duo = lib.mkIf cfg.ssh.enable {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" ];
+      unitConfig.DefaultDependencies = false;
+      script = ''
         if test -f "${cfg.secretKeyFile}"; then
           mkdir -m 0755 -p /etc/duo
 
diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix
index f75b4df1851..95975943ff8 100644
--- a/nixos/modules/security/google_oslogin.nix
+++ b/nixos/modules/security/google_oslogin.nix
@@ -42,6 +42,10 @@ in
     security.sudo.extraConfig = ''
       #includedir /run/google-sudoers.d
     '';
+    security.sudo-rs.extraConfig = ''
+      #includedir /run/google-sudoers.d
+    '';
+
     systemd.tmpfiles.rules = [
       "d /run/google-sudoers.d 750 root root -"
       "d /var/google-users.d 750 root root -"
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index d83259ccbeb..b7e1ea52653 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -6,6 +6,92 @@
 with lib;
 
 let
+
+  mkRulesTypeOption = type: mkOption {
+    # These options are experimental and subject to breaking changes without notice.
+    description = lib.mdDoc ''
+      PAM `${type}` rules for this service.
+
+      Attribute keys are the name of each rule.
+    '';
+    type = types.attrsOf (types.submodule ({ name, config, ... }: {
+      options = {
+        name = mkOption {
+          type = types.str;
+          description = lib.mdDoc ''
+            Name of this rule.
+          '';
+          internal = true;
+          readOnly = true;
+        };
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether this rule is added to the PAM service config file.
+          '';
+        };
+        order = mkOption {
+          type = types.int;
+          description = lib.mdDoc ''
+            Order of this rule in the service file. Rules are arranged in ascending order of this value.
+
+            ::: {.warning}
+            The `order` values for the built-in rules are subject to change. If you assign a constant value to this option, a system update could silently reorder your rule. You could be locked out of your system, or your system could be left wide open. When using this option, set it to a relative offset from another rule's `order` value:
+
+            ```nix
+            {
+              security.pam.services.login.rules.auth.foo.order =
+                config.security.pam.services.login.rules.auth.unix.order + 10;
+            }
+            ```
+            :::
+          '';
+        };
+        control = mkOption {
+          type = types.str;
+          description = lib.mdDoc ''
+            Indicates the behavior of the PAM-API should the module fail to succeed in its authentication task. See `control` in {manpage}`pam.conf(5)` for details.
+          '';
+        };
+        modulePath = mkOption {
+          type = types.str;
+          description = lib.mdDoc ''
+            Either the full filename of the PAM to be used by the application (it begins with a '/'), or a relative pathname from the default module location. See `module-path` in {manpage}`pam.conf(5)` for details.
+          '';
+        };
+        args = mkOption {
+          type = types.listOf types.str;
+          description = lib.mdDoc ''
+            Tokens that can be used to modify the specific behavior of the given PAM. Such arguments will be documented for each individual module. See `module-arguments` in {manpage}`pam.conf(5)` for details.
+
+            Escaping rules for spaces and square brackets are automatically applied.
+
+            {option}`settings` are automatically added as {option}`args`. It's recommended to use the {option}`settings` option whenever possible so that arguments can be overridden.
+          '';
+        };
+        settings = mkOption {
+          type = with types; attrsOf (nullOr (oneOf [ bool str int pathInStore ]));
+          default = {};
+          description = lib.mdDoc ''
+            Settings to add as `module-arguments`.
+
+            Boolean values render just the key if true, and nothing if false. Null values are ignored. All other values are rendered as key-value pairs.
+          '';
+        };
+      };
+      config = {
+        inherit name;
+        # Formats an attrset of settings as args for use as `module-arguments`.
+        args = concatLists (flip mapAttrsToList config.settings (name: value:
+          if isBool value
+          then optional value name
+          else optional (value != null) "${name}=${toString value}"
+        ));
+      };
+    }));
+  };
+
   parentConfig = config;
 
   pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in {
@@ -18,6 +104,28 @@ let
         description = lib.mdDoc "Name of the PAM service.";
       };
 
+      rules = mkOption {
+        # This option is experimental and subject to breaking changes without notice.
+        visible = false;
+
+        description = lib.mdDoc ''
+          PAM rules for this service.
+
+          ::: {.warning}
+          This option and its suboptions are experimental and subject to breaking changes without notice.
+
+          If you use this option in your system configuration, you will need to manually monitor this module for any changes. Otherwise, failure to adjust your configuration properly could lead to you being locked out of your system, or worse, your system could be left wide open to attackers.
+
+          If you share configuration examples that use this option, you MUST include this warning so that users are informed.
+
+          You may freely use this option within `nixpkgs`, and future changes will account for those use sites.
+          :::
+        '';
+        type = types.submodule {
+          options = genAttrs [ "account" "auth" "password" "session" ] mkRulesTypeOption;
+        };
+      };
+
       unixAuth = mkOption {
         default = true;
         type = types.bool;
@@ -470,90 +578,114 @@ let
       setLoginUid = mkDefault cfg.startSession;
       limits = mkDefault config.security.pam.loginLimits;
 
+      text = let
+        ensureUniqueOrder = type: rules:
+          let
+            checkPair = a: b: assert assertMsg (a.order != b.order) "security.pam.services.${name}.rules.${type}: rules '${a.name}' and '${b.name}' cannot have the same order value (${toString a.order})"; b;
+            checked = zipListsWith checkPair rules (drop 1 rules);
+          in take 1 rules ++ checked;
+        # Formats a string for use in `module-arguments`. See `man pam.conf`.
+        formatModuleArgument = token:
+          if hasInfix " " token
+          then "[${replaceStrings ["]"] ["\\]"] token}]"
+          else token;
+        formatRules = type: pipe cfg.rules.${type} [
+          attrValues
+          (filter (rule: rule.enable))
+          (sort (a: b: a.order < b.order))
+          (ensureUniqueOrder type)
+          (map (rule: concatStringsSep " " (
+            [ type rule.control rule.modulePath ]
+            ++ map formatModuleArgument rule.args
+            ++ [ "# ${rule.name} (order ${toString rule.order})" ]
+          )))
+          (concatStringsSep "\n")
+        ];
+      in mkDefault ''
+        # Account management.
+        ${formatRules "account"}
+
+        # Authentication management.
+        ${formatRules "auth"}
+
+        # Password management.
+        ${formatRules "password"}
+
+        # Session management.
+        ${formatRules "session"}
+      '';
+
       # !!! TODO: move the LDAP stuff to the LDAP module, and the
       # Samba stuff to the Samba module.  This requires that the PAM
       # module provides the right hooks.
-      text = mkDefault
-        (
-          ''
-            # Account management.
-          '' +
-          optionalString use_ldap ''
-            account sufficient ${pam_ldap}/lib/security/pam_ldap.so
-          '' +
-          optionalString cfg.mysqlAuth ''
-            account sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
-          '' +
-          optionalString (config.services.kanidm.enablePam) ''
-            account sufficient ${pkgs.kanidm}/lib/pam_kanidm.so ignore_unknown_user
-          '' +
-          optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false) ''
-            account sufficient ${pkgs.sssd}/lib/security/pam_sss.so
-          '' +
-          optionalString (config.services.sssd.enable && cfg.sssdStrictAccess) ''
-            account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so
-          '' +
-          optionalString config.security.pam.krb5.enable ''
-            account sufficient ${pam_krb5}/lib/security/pam_krb5.so
-          '' +
-          optionalString cfg.googleOsLoginAccountVerification ''
-            account [success=ok ignore=ignore default=die] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
-            account [success=ok default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so
-          '' +
-          optionalString config.services.homed.enable ''
-            account sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
-          '' +
+      rules = let
+        autoOrderRules = flip pipe [
+          (imap1 (index: rule: rule // { order = mkDefault (10000 + index * 100); } ))
+          (map (rule: nameValuePair rule.name (removeAttrs rule [ "name" ])))
+          listToAttrs
+        ];
+      in {
+        account = autoOrderRules [
+          { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
+          { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
+            config_file = "/etc/security/pam_mysql.conf";
+          }; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; settings = {
+            ignore_unknown_user = true;
+          }; }
+          { name = "sss"; enable = config.services.sssd.enable; control = if cfg.sssdStrictAccess then "[default=bad success=ok user_unknown=ignore]" else "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
+          { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
+          { name = "oslogin_login"; enable = cfg.googleOsLoginAccountVerification; control = "[success=ok ignore=ignore default=die]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
+          { name = "oslogin_admin"; enable = cfg.googleOsLoginAccountVerification; control = "[success=ok default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so"; }
+          { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
           # The required pam_unix.so module has to come after all the sufficient modules
           # because otherwise, the account lookup will fail if the user does not exist
           # locally, for example with MySQL- or LDAP-auth.
-          ''
-            account required pam_unix.so
-
-            # Authentication management.
-          '' +
-          optionalString cfg.googleOsLoginAuthentication ''
-            auth [success=done perm_denied=die default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
-          '' +
-          optionalString cfg.rootOK ''
-            auth sufficient pam_rootok.so
-          '' +
-          optionalString cfg.requireWheel ''
-            auth required pam_wheel.so use_uid
-          '' +
-          optionalString cfg.logFailures ''
-            auth required pam_faillock.so
-          '' +
-          optionalString cfg.mysqlAuth ''
-            auth sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
-          '' +
-          optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth) ''
-            auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=${lib.concatStringsSep ":" config.services.openssh.authorizedKeysFiles}
-          '' +
-          (let p11 = config.security.pam.p11; in optionalString cfg.p11Auth ''
-            auth ${p11.control} ${pkgs.pam_p11}/lib/security/pam_p11.so ${pkgs.opensc}/lib/opensc-pkcs11.so
-          '') +
-          (let u2f = config.security.pam.u2f; in optionalString cfg.u2fAuth (''
-              auth ${u2f.control} ${pkgs.pam_u2f}/lib/security/pam_u2f.so ${optionalString u2f.debug "debug"} ${optionalString (u2f.authFile != null) "authfile=${u2f.authFile}"} ''
-                + ''${optionalString u2f.interactive "interactive"} ${optionalString u2f.cue "cue"} ${optionalString (u2f.appId != null) "appid=${u2f.appId}"} ${optionalString (u2f.origin != null) "origin=${u2f.origin}"}
-          '')) +
-          optionalString cfg.usbAuth ''
-            auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so
-          '' +
-          (let ussh = config.security.pam.ussh; in optionalString (config.security.pam.ussh.enable && cfg.usshAuth) ''
-            auth ${ussh.control} ${pkgs.pam_ussh}/lib/security/pam_ussh.so ${optionalString (ussh.caFile != null) "ca_file=${ussh.caFile}"} ${optionalString (ussh.authorizedPrincipals != null) "authorized_principals=${ussh.authorizedPrincipals}"} ${optionalString (ussh.authorizedPrincipalsFile != null) "authorized_principals_file=${ussh.authorizedPrincipalsFile}"} ${optionalString (ussh.group != null) "group=${ussh.group}"}
-          '') +
-          (let oath = config.security.pam.oath; in optionalString cfg.oathAuth ''
-            auth requisite ${pkgs.oath-toolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}
-          '') +
-          (let yubi = config.security.pam.yubico; in optionalString cfg.yubicoAuth ''
-            auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so mode=${toString yubi.mode} ${optionalString (yubi.challengeResponsePath != null) "chalresp_path=${yubi.challengeResponsePath}"} ${optionalString (yubi.mode == "client") "id=${toString yubi.id}"} ${optionalString yubi.debug "debug"}
-          '') +
-          (let dp9ik = config.security.pam.dp9ik; in optionalString dp9ik.enable ''
-            auth ${dp9ik.control} ${pkgs.pam_dp9ik}/lib/security/pam_p9.so ${dp9ik.authserver}
-          '') +
-          optionalString cfg.fprintAuth ''
-            auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so
-          '' +
+          { name = "unix"; control = "required"; modulePath = "pam_unix.so"; }
+        ];
+
+        auth = autoOrderRules ([
+          { name = "oslogin_login"; enable = cfg.googleOsLoginAuthentication; control = "[success=done perm_denied=die default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
+          { name = "rootok"; enable = cfg.rootOK; control = "sufficient"; modulePath = "pam_rootok.so"; }
+          { name = "wheel"; enable = cfg.requireWheel; control = "required"; modulePath = "pam_wheel.so"; settings = {
+            use_uid = true;
+          }; }
+          { name = "faillock"; enable = cfg.logFailures; control = "required"; modulePath = "pam_faillock.so"; }
+          { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
+            config_file = "/etc/security/pam_mysql.conf";
+          }; }
+          { name = "ssh_agent_auth"; enable = config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth; control = "sufficient"; modulePath = "${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so"; settings = {
+            file = lib.concatStringsSep ":" config.services.openssh.authorizedKeysFiles;
+          }; }
+          (let p11 = config.security.pam.p11; in { name = "p11"; enable = cfg.p11Auth; control = p11.control; modulePath = "${pkgs.pam_p11}/lib/security/pam_p11.so"; args = [
+            "${pkgs.opensc}/lib/opensc-pkcs11.so"
+          ]; })
+          (let u2f = config.security.pam.u2f; in { name = "u2f"; enable = cfg.u2fAuth; control = u2f.control; modulePath = "${pkgs.pam_u2f}/lib/security/pam_u2f.so"; settings = {
+            inherit (u2f) debug interactive cue origin;
+            authfile = u2f.authFile;
+            appid = u2f.appId;
+          }; })
+          { name = "usb"; enable = cfg.usbAuth; control = "sufficient"; modulePath = "${pkgs.pam_usb}/lib/security/pam_usb.so"; }
+          (let ussh = config.security.pam.ussh; in { name = "ussh"; enable = config.security.pam.ussh.enable && cfg.usshAuth; control = ussh.control; modulePath = "${pkgs.pam_ussh}/lib/security/pam_ussh.so"; settings = {
+            ca_file = ussh.caFile;
+            authorized_principals = ussh.authorizedPrincipals;
+            authorized_principals_file = ussh.authorizedPrincipalsFile;
+            inherit (ussh) group;
+          }; })
+          (let oath = config.security.pam.oath; in { name = "oath"; enable = cfg.oathAuth; control = "requisite"; modulePath = "${pkgs.oath-toolkit}/lib/security/pam_oath.so"; settings = {
+            inherit (oath) window digits;
+            usersfile = oath.usersFile;
+          }; })
+          (let yubi = config.security.pam.yubico; in { name = "yubico"; enable = cfg.yubicoAuth; control = yubi.control; modulePath = "${pkgs.yubico-pam}/lib/security/pam_yubico.so"; settings = {
+            inherit (yubi) mode debug;
+            chalresp_path = yubi.challengeResponsePath;
+            id = mkIf (yubi.mode == "client") yubi.id;
+          }; })
+          (let dp9ik = config.security.pam.dp9ik; in { name = "p9"; enable = dp9ik.enable; control = dp9ik.control; modulePath = "${pkgs.pam_dp9ik}/lib/security/pam_p9.so"; args = [
+            dp9ik.authserver
+          ]; })
+          { name = "fprintd"; enable = cfg.fprintAuth; control = "sufficient"; modulePath = "${pkgs.fprintd}/lib/security/pam_fprintd.so"; }
+        ] ++
           # Modules in this block require having the password set in PAM_AUTHTOK.
           # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
           # after it succeeds. Certain modules need to run after pam_unix
@@ -562,7 +694,7 @@ let
           # We use try_first_pass the second time to avoid prompting password twice.
           #
           # The same principle applies to systemd-homed
-          (optionalString ((cfg.unixAuth || config.services.homed.enable) &&
+          (optionals ((cfg.unixAuth || config.services.homed.enable) &&
             (config.security.pam.enableEcryptfs
               || config.security.pam.enableFscrypt
               || cfg.pamMount
@@ -573,199 +705,173 @@ let
               || cfg.failDelay.enable
               || cfg.duoSecurity.enable
               || cfg.zfs))
-            (
-              optionalString config.services.homed.enable ''
-                auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so
-              '' +
-              optionalString cfg.unixAuth ''
-                auth optional pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
-              '' +
-              optionalString config.security.pam.enableEcryptfs ''
-                auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap
-              '' +
-              optionalString config.security.pam.enableFscrypt ''
-                auth optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
-              '' +
-              optionalString cfg.zfs ''
-                auth optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes}
-              '' +
-              optionalString cfg.pamMount ''
-                auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
-              '' +
-              optionalString cfg.enableKwallet ''
-               auth optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5
-              '' +
-              optionalString cfg.enableGnomeKeyring ''
-                auth optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so
-              '' +
-              optionalString cfg.gnupg.enable ''
-                auth optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.storeOnly " store-only"}
-              '' +
-              optionalString cfg.failDelay.enable ''
-                auth optional ${pkgs.pam}/lib/security/pam_faildelay.so delay=${toString cfg.failDelay.delay}
-              '' +
-              optionalString cfg.googleAuthenticator.enable ''
-                auth required ${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so no_increment_hotp
-              '' +
-              optionalString cfg.duoSecurity.enable ''
-                auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
-              ''
-            )) +
-          optionalString config.services.homed.enable ''
-            auth sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
-          '' +
-          optionalString cfg.unixAuth ''
-            auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
-          '' +
-          optionalString cfg.otpwAuth ''
-            auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so
-          '' +
-          optionalString use_ldap ''
-            auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass
-          '' +
-          optionalString config.services.kanidm.enablePam ''
-            auth sufficient ${pkgs.kanidm}/lib/pam_kanidm.so ignore_unknown_user use_first_pass
-          '' +
-          optionalString config.services.sssd.enable ''
-            auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass
-          '' +
-          optionalString config.security.pam.krb5.enable ''
-            auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
-            auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass
-            auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass
-          '' +
-          ''
-            auth required pam_deny.so
-
-            # Password management.
-          '' +
-          optionalString config.services.homed.enable ''
-            password sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
-          '' + ''
-            password sufficient pam_unix.so nullok yescrypt
-          '' +
-          optionalString config.security.pam.enableEcryptfs ''
-            password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
-          '' +
-          optionalString config.security.pam.enableFscrypt ''
-            password optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
-          '' +
-          optionalString cfg.zfs ''
-            password optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes}
-          '' +
-          optionalString cfg.pamMount ''
-            password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
-          '' +
-          optionalString use_ldap ''
-            password sufficient ${pam_ldap}/lib/security/pam_ldap.so
-          '' +
-          optionalString cfg.mysqlAuth ''
-            password sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
-          '' +
-          optionalString config.services.kanidm.enablePam ''
-            password sufficient ${pkgs.kanidm}/lib/pam_kanidm.so
-          '' +
-          optionalString config.services.sssd.enable ''
-            password sufficient ${pkgs.sssd}/lib/security/pam_sss.so
-          '' +
-          optionalString config.security.pam.krb5.enable ''
-            password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
-          '' +
-          optionalString cfg.enableGnomeKeyring ''
-            password optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so use_authtok
-          '' +
-          ''
+            [
+              { name = "systemd_home-early"; enable = config.services.homed.enable; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
+              { name = "unix-early"; enable = cfg.unixAuth; control = "optional"; modulePath = "pam_unix.so"; settings = {
+                nullok = cfg.allowNullPassword;
+                inherit (cfg) nodelay;
+                likeauth = true;
+              }; }
+              { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; settings = {
+                unwrap = true;
+              }; }
+              { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
+              { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
+                inherit (config.security.pam.zfs) homes;
+              }; }
+              { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
+                disable_interactive = true;
+              }; }
+              { name = "kwallet5"; enable = cfg.enableKwallet; control = "optional"; modulePath = "${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so"; settings = {
+                kwalletd = "${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5";
+              }; }
+              { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; }
+              { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
+                store-only = cfg.gnupg.storeOnly;
+              }; }
+              { name = "faildelay"; enable = cfg.failDelay.enable; control = "optional"; modulePath = "${pkgs.pam}/lib/security/pam_faildelay.so"; settings = {
+                inherit (cfg.failDelay) delay;
+              }; }
+              { name = "google_authenticator"; enable = cfg.googleAuthenticator.enable; control = "required"; modulePath = "${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so"; settings = {
+                no_increment_hotp = true;
+              }; }
+              { name = "duo"; enable = cfg.duoSecurity.enable; control = "required"; modulePath = "${pkgs.duo-unix}/lib/security/pam_duo.so"; }
+            ]) ++ [
+          { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
+          { name = "unix"; enable = cfg.unixAuth; control = "sufficient"; modulePath = "pam_unix.so"; settings = {
+            nullok = cfg.allowNullPassword;
+            inherit (cfg) nodelay;
+            likeauth = true;
+            try_first_pass = true;
+          }; }
+          { name = "otpw"; enable = cfg.otpwAuth; control = "sufficient"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
+          { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; settings = {
+            use_first_pass = true;
+          }; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; settings = {
+            ignore_unknown_user = true;
+            use_first_pass = true;
+          }; }
+          { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; settings = {
+            use_first_pass = true;
+          }; }
+          { name = "krb5"; enable = config.security.pam.krb5.enable; control = "[default=ignore success=1 service_err=reset]"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; settings = {
+            use_first_pass = true;
+          }; }
+          { name = "ccreds-validate"; enable = config.security.pam.krb5.enable; control = "[default=die success=done]"; modulePath = "${pam_ccreds}/lib/security/pam_ccreds.so"; settings = {
+            action = "validate";
+            use_first_pass = true;
+          }; }
+          { name = "ccreds-store"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_ccreds}/lib/security/pam_ccreds.so"; settings = {
+            action = "store";
+            use_first_pass = true;
+          }; }
+          { name = "deny"; control = "required"; modulePath = "pam_deny.so"; }
+        ]);
+
+        password = autoOrderRules [
+          { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
+          { name = "unix"; control = "sufficient"; modulePath = "pam_unix.so"; settings = {
+            nullok = true;
+            yescrypt = true;
+          }; }
+          { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
+          { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
+          { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
+            inherit (config.security.pam.zfs) homes;
+          }; }
+          { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; }
+          { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
+          { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
+            config_file = "/etc/security/pam_mysql.conf";
+          }; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; }
+          { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
+          { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; settings = {
+            use_first_pass = true;
+          }; }
+          { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
+            use_authtok = true;
+          }; }
+        ];
 
-            # Session management.
-          '' +
-          optionalString cfg.setEnvironment ''
-            session required pam_env.so conffile=/etc/pam/environment readenv=0
-          '' +
-          ''
-            session required pam_unix.so
-          '' +
-          optionalString cfg.setLoginUid ''
-            session ${if config.boot.isContainer then "optional" else "required"} pam_loginuid.so
-          '' +
-          optionalString cfg.ttyAudit.enable (concatStringsSep " \\\n  " ([
-            "session required ${pkgs.pam}/lib/security/pam_tty_audit.so"
-          ] ++ optional cfg.ttyAudit.openOnly "open_only"
-          ++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"
-          ++ optional (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}"
-          )) +
-          optionalString config.services.homed.enable ''
-            session required ${config.systemd.package}/lib/security/pam_systemd_home.so
-          '' +
-          optionalString cfg.makeHomeDir ''
-            session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=${config.security.pam.makeHomeDir.umask}
-          '' +
-          optionalString cfg.updateWtmp ''
-            session required ${pkgs.pam}/lib/security/pam_lastlog.so silent
-          '' +
-          optionalString config.security.pam.enableEcryptfs ''
-            session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
-          '' +
-          optionalString config.security.pam.enableFscrypt ''
-            # Work around https://github.com/systemd/systemd/issues/8598
-            # Skips the pam_fscrypt module for systemd-user sessions which do not have a password
-            # anyways.
-            # See also https://github.com/google/fscrypt/issues/95
-            session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
-            session optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
-          '' +
-          optionalString cfg.zfs ''
-            session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
-            session optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes} ${optionalString config.security.pam.zfs.noUnmount "nounmount"}
-          '' +
-          optionalString cfg.pamMount ''
-            session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
-          '' +
-          optionalString use_ldap ''
-            session optional ${pam_ldap}/lib/security/pam_ldap.so
-          '' +
-          optionalString cfg.mysqlAuth ''
-            session optional ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
-          '' +
-          optionalString config.services.kanidm.enablePam ''
-            session optional ${pkgs.kanidm}/lib/pam_kanidm.so
-          '' +
-          optionalString config.services.sssd.enable ''
-            session optional ${pkgs.sssd}/lib/security/pam_sss.so
-          '' +
-          optionalString config.security.pam.krb5.enable ''
-            session optional ${pam_krb5}/lib/security/pam_krb5.so
-          '' +
-          optionalString cfg.otpwAuth ''
-            session optional ${pkgs.otpw}/lib/security/pam_otpw.so
-          '' +
-          optionalString cfg.startSession ''
-            session optional ${config.systemd.package}/lib/security/pam_systemd.so
-          '' +
-          optionalString cfg.forwardXAuth ''
-            session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99
-          '' +
-          optionalString (cfg.limits != []) ''
-            session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}
-          '' +
-          optionalString (cfg.showMotd && (config.users.motd != null || config.users.motdFile != null)) ''
-            session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}
-          '' +
-          optionalString (cfg.enableAppArmor && config.security.apparmor.enable) ''
-            session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug
-          '' +
-          optionalString (cfg.enableKwallet) ''
-            session optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5
-          '' +
-          optionalString (cfg.enableGnomeKeyring) ''
-            session optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start
-          '' +
-          optionalString cfg.gnupg.enable ''
-            session optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.noAutostart " no-autostart"}
-          '' +
-          optionalString (config.virtualisation.lxc.lxcfs.enable) ''
-            session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all
-          ''
-        );
+        session = autoOrderRules [
+          { name = "env"; enable = cfg.setEnvironment; control = "required"; modulePath = "pam_env.so"; settings = {
+            conffile = "/etc/pam/environment";
+            readenv = 0;
+          }; }
+          { name = "unix"; control = "required"; modulePath = "pam_unix.so"; }
+          { name = "loginuid"; enable = cfg.setLoginUid; control = if config.boot.isContainer then "optional" else "required"; modulePath = "pam_loginuid.so"; }
+          { name = "tty_audit"; enable = cfg.ttyAudit.enable; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_tty_audit.so"; settings = {
+            open_only = cfg.ttyAudit.openOnly;
+            enable = cfg.ttyAudit.enablePattern;
+            disable = cfg.ttyAudit.disablePattern;
+          }; }
+          { name = "systemd_home"; enable = config.services.homed.enable; control = "required"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
+          { name = "mkhomedir"; enable = cfg.makeHomeDir; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_mkhomedir.so"; settings = {
+            silent = true;
+            skel = config.security.pam.makeHomeDir.skelDirectory;
+            inherit (config.security.pam.makeHomeDir) umask;
+          }; }
+          { name = "lastlog"; enable = cfg.updateWtmp; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_lastlog.so"; settings = {
+            silent = true;
+          }; }
+          { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
+          # Work around https://github.com/systemd/systemd/issues/8598
+          # Skips the pam_fscrypt module for systemd-user sessions which do not have a password
+          # anyways.
+          # See also https://github.com/google/fscrypt/issues/95
+          { name = "fscrypt-skip-systemd"; enable = config.security.pam.enableFscrypt; control = "[success=1 default=ignore]"; modulePath = "pam_succeed_if.so"; args = [
+            "service" "=" "systemd-user"
+          ]; }
+          { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
+          { name = "zfs_key-skip-systemd"; enable = cfg.zfs; control = "[success=1 default=ignore]"; modulePath = "pam_succeed_if.so"; args = [
+            "service" "=" "systemd-user"
+          ]; }
+          { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
+            inherit (config.security.pam.zfs) homes;
+            nounmount = config.security.pam.zfs.noUnmount;
+          }; }
+          { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
+            disable_interactive = true;
+          }; }
+          { name = "ldap"; enable = use_ldap; control = "optional"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
+          { name = "mysql"; enable = cfg.mysqlAuth; control = "optional"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
+            config_file = "/etc/security/pam_mysql.conf";
+          }; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "optional"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; }
+          { name = "sss"; enable = config.services.sssd.enable; control = "optional"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
+          { name = "krb5"; enable = config.security.pam.krb5.enable; control = "optional"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
+          { name = "otpw"; enable = cfg.otpwAuth; control = "optional"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
+          { name = "systemd"; enable = cfg.startSession; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd.so"; }
+          { name = "xauth"; enable = cfg.forwardXAuth; control = "optional"; modulePath = "pam_xauth.so"; settings = {
+            xauthpath = "${pkgs.xorg.xauth}/bin/xauth";
+            systemuser = 99;
+          }; }
+          { name = "limits"; enable = cfg.limits != []; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_limits.so"; settings = {
+            conf = "${makeLimitsConf cfg.limits}";
+          }; }
+          { name = "motd"; enable = cfg.showMotd && (config.users.motd != null || config.users.motdFile != null); control = "optional"; modulePath = "${pkgs.pam}/lib/security/pam_motd.so"; settings = {
+            inherit motd;
+          }; }
+          { name = "apparmor"; enable = cfg.enableAppArmor && config.security.apparmor.enable; control = "optional"; modulePath = "${pkgs.apparmor-pam}/lib/security/pam_apparmor.so"; settings = {
+            order = "user,group,default";
+            debug = true;
+          }; }
+          { name = "kwallet5"; enable = cfg.enableKwallet; control = "optional"; modulePath = "${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so"; settings = {
+            kwalletd = "${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5";
+          }; }
+          { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
+            auto_start = true;
+          }; }
+          { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
+            no-autostart = cfg.gnupg.noAutostart;
+          }; }
+          { name = "cgfs"; enable = config.virtualisation.lxc.lxcfs.enable; control = "optional"; modulePath = "${pkgs.lxc}/lib/security/pam_cgfs.so"; args = [
+            "-c" "all"
+          ]; }
+        ];
+      };
     };
 
   };
@@ -841,6 +947,8 @@ in
 
 {
 
+  meta.maintainers = [ maintainers.majiir ];
+
   imports = [
     (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
   ];
@@ -1402,9 +1510,7 @@ in
         fscrypt = {};
       };
 
-    security.apparmor.includes."abstractions/pam" = let
-      isEnabled = test: fold or false (map test (attrValues config.security.pam.services));
-      in
+    security.apparmor.includes."abstractions/pam" =
       lib.concatMapStrings
         (name: "r ${config.environment.etc."pam.d/${name}".source},\n")
         (attrNames config.security.pam.services) +
@@ -1413,88 +1519,22 @@ in
       mr ${getLib pkgs.pam}/lib/security/pam_*.so,
       r ${getLib pkgs.pam}/lib/security/,
       '' +
-      optionalString use_ldap ''
-         mr ${pam_ldap}/lib/security/pam_ldap.so,
-      '' +
-      optionalString config.services.kanidm.enablePam ''
-        mr ${pkgs.kanidm}/lib/pam_kanidm.so,
-      '' +
-      optionalString config.services.sssd.enable ''
-        mr ${pkgs.sssd}/lib/security/pam_sss.so,
-      '' +
-      optionalString config.security.pam.krb5.enable ''
-        mr ${pam_krb5}/lib/security/pam_krb5.so,
-        mr ${pam_ccreds}/lib/security/pam_ccreds.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.googleOsLoginAccountVerification)) ''
-        mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so,
-        mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.googleOsLoginAuthentication)) ''
-        mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so,
-      '' +
-      optionalString (config.security.pam.enableSSHAgentAuth
-                     && isEnabled (cfg: cfg.sshAgentAuth)) ''
-        mr ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.fprintAuth)) ''
-        mr ${pkgs.fprintd}/lib/security/pam_fprintd.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.u2fAuth)) ''
-        mr ${pkgs.pam_u2f}/lib/security/pam_u2f.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.usbAuth)) ''
-        mr ${pkgs.pam_usb}/lib/security/pam_usb.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.usshAuth)) ''
-        mr ${pkgs.pam_ussh}/lib/security/pam_ussh.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.oathAuth)) ''
-        "mr ${pkgs.oath-toolkit}/lib/security/pam_oath.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.mysqlAuth)) ''
-        mr ${pkgs.pam_mysql}/lib/security/pam_mysql.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.yubicoAuth)) ''
-        mr ${pkgs.yubico-pam}/lib/security/pam_yubico.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.duoSecurity.enable)) ''
-        mr ${pkgs.duo-unix}/lib/security/pam_duo.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.otpwAuth)) ''
-        mr ${pkgs.otpw}/lib/security/pam_otpw.so,
-      '' +
-      optionalString config.security.pam.enableEcryptfs ''
-        mr ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so,
-      '' +
-      optionalString config.security.pam.enableFscrypt ''
-        mr ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.pamMount)) ''
-        mr ${pkgs.pam_mount}/lib/security/pam_mount.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.enableGnomeKeyring)) ''
-        mr ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.startSession)) ''
-        mr ${config.systemd.package}/lib/security/pam_systemd.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.enableAppArmor)
-                     && config.security.apparmor.enable) ''
-        mr ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.enableKwallet)) ''
-        mr ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so,
-      '' +
-      optionalString config.virtualisation.lxc.lxcfs.enable ''
-        mr ${pkgs.lxc}/lib/security/pam_cgfs.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.zfs)) ''
-        mr ${config.boot.zfs.package}/lib/security/pam_zfs_key.so,
-      '' +
-      optionalString config.services.homed.enable ''
-        mr ${config.systemd.package}/lib/security/pam_systemd_home.so
-      '';
-  };
-
+      (with lib; pipe config.security.pam.services [
+        attrValues
+        (catAttrs "rules")
+        (concatMap attrValues)
+        (concatMap attrValues)
+        (filter (rule: rule.enable))
+        (catAttrs "modulePath")
+        (filter (hasPrefix "/"))
+        unique
+        (map (module: "mr ${module},"))
+        concatLines
+      ]);
+
+    security.sudo.extraConfig = optionalString config.security.pam.enableSSHAgentAuth ''
+      # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
+      Defaults env_keep+=SSH_AUTH_SOCK
+    '';
+    };
 }
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index ad78f38b086..26f906f2a76 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -33,7 +33,7 @@ in
         default = [];
         description = lib.mdDoc ''
           List of volume definitions for pam_mount.
-          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
@@ -78,7 +78,7 @@ in
         description = lib.mdDoc ''
           Sets the Debug-Level. 0 disables debugging, 1 enables pam_mount tracing,
           and 2 additionally enables tracing in mount.crypt. The default is 0.
-          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
@@ -88,7 +88,7 @@ in
         description = lib.mdDoc ''
           Amount of microseconds to wait until killing remaining processes after
           final logout.
-          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index de427ccb295..327f49c0b63 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -35,7 +35,7 @@ in
       description = lib.mdDoc
         ''
           Any polkit rules to be added to config (in JavaScript ;-). See:
-          http://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules
+          <https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules>
         '';
     };
 
@@ -117,4 +117,3 @@ in
   };
 
 }
-
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index d225442773c..3dd5d2e525d 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.security.sudo;
 
-  inherit (pkgs) sudo;
+  inherit (config.security.pam) enableSSHAgentAuth;
 
   toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
   toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
@@ -30,9 +30,18 @@ in
 
   ###### interface
 
-  options = {
+  options.security.sudo = {
 
-    security.sudo.enable = mkOption {
+    defaultOptions = mkOption {
+      type = with types; listOf str;
+      default = [ "SETENV" ];
+      description = mdDoc ''
+        Options used for the default rules, granting `root` and the
+        `wheel` group permission to run any command as any user.
+      '';
+    };
+
+    enable = mkOption {
       type = types.bool;
       default = true;
       description =
@@ -42,29 +51,21 @@ in
         '';
     };
 
-    security.sudo.package = mkOption {
-      type = types.package;
-      default = pkgs.sudo;
-      defaultText = literalExpression "pkgs.sudo";
-      description = lib.mdDoc ''
-        Which package to use for `sudo`.
-      '';
-    };
+    package = mkPackageOption pkgs "sudo" { };
 
-    security.sudo.wheelNeedsPassword = mkOption {
+    wheelNeedsPassword = mkOption {
       type = types.bool;
       default = true;
-      description =
-        lib.mdDoc ''
-          Whether users of the `wheel` group must
-          provide a password to run commands as super user via {command}`sudo`.
-        '';
+      description = mdDoc ''
+        Whether users of the `wheel` group must
+        provide a password to run commands as super user via {command}`sudo`.
+      '';
       };
 
-    security.sudo.execWheelOnly = mkOption {
+    execWheelOnly = mkOption {
       type = types.bool;
       default = false;
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Only allow members of the `wheel` group to execute sudo by
         setting the executable's permissions accordingly.
         This prevents users that are not members of `wheel` from
@@ -72,19 +73,18 @@ in
       '';
     };
 
-    security.sudo.configFile = mkOption {
+    configFile = mkOption {
       type = types.lines;
       # Note: if syntax errors are detected in this file, the NixOS
       # configuration will fail to build.
-      description =
-        lib.mdDoc ''
-          This string contains the contents of the
-          {file}`sudoers` file.
-        '';
+      description = mdDoc ''
+        This string contains the contents of the
+        {file}`sudoers` file.
+      '';
     };
 
-    security.sudo.extraRules = mkOption {
-      description = lib.mdDoc ''
+    extraRules = mkOption {
+      description = mdDoc ''
         Define specific rules to be in the {file}`sudoers` file.
         More specific rules should come after more general ones in order to
         yield the expected behavior. You can use mkBefore/mkAfter to ensure
@@ -114,7 +114,7 @@ in
         options = {
           users = mkOption {
             type = with types; listOf (either str int);
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The usernames / UIDs this rule should apply for.
             '';
             default = [];
@@ -122,7 +122,7 @@ in
 
           groups = mkOption {
             type = with types; listOf (either str int);
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The groups / GIDs this rule should apply for.
             '';
             default = [];
@@ -131,7 +131,7 @@ in
           host = mkOption {
             type = types.str;
             default = "ALL";
-            description = lib.mdDoc ''
+            description = mdDoc ''
               For what host this rule should apply.
             '';
           };
@@ -139,7 +139,7 @@ in
           runAs = mkOption {
             type = with types; str;
             default = "ALL:ALL";
-            description = lib.mdDoc ''
+            description = mdDoc ''
               Under which user/group the specified command is allowed to run.
 
               A user can be specified using just the username: `"foo"`.
@@ -149,7 +149,7 @@ in
           };
 
           commands = mkOption {
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The commands for which the rule should apply.
             '';
             type = with types; listOf (either str (submodule {
@@ -157,7 +157,7 @@ in
               options = {
                 command = mkOption {
                   type = with types; str;
-                  description = lib.mdDoc ''
+                  description = mdDoc ''
                     A command being either just a path to a binary to allow any arguments,
                     the full command with arguments pre-set or with `""` used as the argument,
                     not allowing arguments to the command at all.
@@ -166,7 +166,7 @@ in
 
                 options = mkOption {
                   type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
-                  description = lib.mdDoc ''
+                  description = mdDoc ''
                     Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
                   '';
                   default = [];
@@ -179,10 +179,10 @@ in
       });
     };
 
-    security.sudo.extraConfig = mkOption {
+    extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Extra configuration text appended to {file}`sudoers`.
       '';
     };
@@ -192,44 +192,55 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.package.pname != "sudo-rs";
-        message = "The NixOS `sudo` module does not work with `sudo-rs` yet."; }
-    ];
-
-    # We `mkOrder 600` so that the default rule shows up first, but there is
-    # still enough room for a user to `mkBefore` it.
-    security.sudo.extraRules = mkOrder 600 [
-      { groups = [ "wheel" ];
-        commands = [ { command = "ALL"; options = (if cfg.wheelNeedsPassword then [ "SETENV" ] else [ "NOPASSWD" "SETENV" ]); } ];
-      }
-    ];
-
-    security.sudo.configFile =
+    assertions = [ {
+      assertion = cfg.package.pname != "sudo-rs";
+      message = ''
+        NixOS' `sudo` module does not support `sudo-rs`; see `security.sudo-rs` instead.
+      '';
+    } ];
+
+    security.sudo.extraRules =
+      let
+        defaultRule = { users ? [], groups ? [], opts ? [] }: [ {
+          inherit users groups;
+          commands = [ {
+            command = "ALL";
+            options = opts ++ cfg.defaultOptions;
+          } ];
+        } ];
+      in mkMerge [
+        # This is ordered before users' `mkBefore` rules,
+        # so as not to introduce unexpected changes.
+        (mkOrder 400 (defaultRule { users = [ "root" ]; }))
+
+        # This is ordered to show before (most) other rules, but
+        # late-enough for a user to `mkBefore` it.
+        (mkOrder 600 (defaultRule {
+          groups = [ "wheel" ];
+          opts = (optional (!cfg.wheelNeedsPassword) "NOPASSWD");
+        }))
+      ];
+
+    security.sudo.configFile = concatStringsSep "\n" (filter (s: s != "") [
       ''
         # Don't edit this file. Set the NixOS options ‘security.sudo.configFile’
         # or ‘security.sudo.extraRules’ instead.
-
-        # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
-        Defaults env_keep+=SSH_AUTH_SOCK
-
-        # "root" is allowed to do anything.
-        root        ALL=(ALL:ALL) SETENV: ALL
-
-        # extraRules
-        ${concatStringsSep "\n" (
-          lists.flatten (
-            map (
-              rule: optionals (length rule.commands != 0) [
-                (map (user: "${toUserString user}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.users)
-                (map (group: "${toGroupString group}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.groups)
-              ]
-            ) cfg.extraRules
-          )
-        )}
-
+      ''
+      (pipe cfg.extraRules [
+        (filter (rule: length rule.commands != 0))
+        (map (rule: [
+          (map (user: "${toUserString user}     ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.users)
+          (map (group: "${toGroupString group}  ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.groups)
+        ]))
+        flatten
+        (concatStringsSep "\n")
+      ])
+      "\n"
+      (optionalString (cfg.extraConfig != "") ''
+        # extraConfig
         ${cfg.extraConfig}
-      '';
+      '')
+    ]);
 
     security.wrappers = let
       owner = "root";
@@ -247,7 +258,7 @@ in
       };
     };
 
-    environment.systemPackages = [ sudo ];
+    environment.systemPackages = [ cfg.package ];
 
     security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
 
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index ad65f80bb2c..250f9775be1 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -5,8 +5,29 @@ let
 
   parentWrapperDir = dirOf wrapperDir;
 
-  securityWrapper = sourceProg : pkgs.callPackage ./wrapper.nix {
+  # This is security-sensitive code, and glibc vulns happen from time to time.
+  # musl is security-focused and generally more minimal, so it's a better choice here.
+  # The dynamic linker is still a fairly complex piece of code, and the wrappers are
+  # quite small, so linking it statically is more appropriate.
+  securityWrapper = sourceProg : pkgs.pkgsStatic.callPackage ./wrapper.nix {
     inherit sourceProg;
+
+    # glibc definitions of insecure environment variables
+    #
+    # We extract the single header file we need into its own derivation,
+    # so that we don't have to pull full glibc sources to build wrappers.
+    #
+    # They're taken from pkgs.glibc so that we don't have to keep as close
+    # an eye on glibc changes. Not every relevant variable is in this header,
+    # so we maintain a slightly stricter list in wrapper.c itself as well.
+    unsecvars = lib.overrideDerivation (pkgs.srcOnly pkgs.glibc)
+      ({ name, ... }: {
+        name = "${name}-unsecvars";
+        installPhase = ''
+          mkdir $out
+          cp sysdeps/generic/unsecvars.h $out
+        '';
+      });
   };
 
   fileModeType =
@@ -254,33 +275,38 @@ in
       mrpx ${wrap.source},
     '') wrappers;
 
-    ###### wrappers activation script
-    system.activationScripts.wrappers =
-      lib.stringAfter [ "specialfs" "users" ]
-        ''
-          chmod 755 "${parentWrapperDir}"
-
-          # We want to place the tmpdirs for the wrappers to the parent dir.
-          wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
-          chmod a+rx "$wrapperDir"
-
-          ${lib.concatStringsSep "\n" mkWrappedPrograms}
-
-          if [ -L ${wrapperDir} ]; then
-            # Atomically replace the symlink
-            # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
-            old=$(readlink -f ${wrapperDir})
-            if [ -e "${wrapperDir}-tmp" ]; then
-              rm --force --recursive "${wrapperDir}-tmp"
-            fi
-            ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp"
-            mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}"
-            rm --force --recursive "$old"
-          else
-            # For initial setup
-            ln --symbolic "$wrapperDir" "${wrapperDir}"
+    systemd.services.suid-sgid-wrappers = {
+      description = "Create SUID/SGID Wrappers";
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" ];
+      unitConfig.DefaultDependencies = false;
+      unitConfig.RequiresMountsFor = [ "/nix/store" "/run/wrappers" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        chmod 755 "${parentWrapperDir}"
+
+        # We want to place the tmpdirs for the wrappers to the parent dir.
+        wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
+        chmod a+rx "$wrapperDir"
+
+        ${lib.concatStringsSep "\n" mkWrappedPrograms}
+
+        if [ -L ${wrapperDir} ]; then
+          # Atomically replace the symlink
+          # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
+          old=$(readlink -f ${wrapperDir})
+          if [ -e "${wrapperDir}-tmp" ]; then
+            rm --force --recursive "${wrapperDir}-tmp"
           fi
-        '';
+          ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp"
+          mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}"
+          rm --force --recursive "$old"
+        else
+          # For initial setup
+          ln --symbolic "$wrapperDir" "${wrapperDir}"
+        fi
+      '';
+    };
 
     ###### wrappers consistency checks
     system.checks = lib.singleton (pkgs.runCommandLocal
diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c
index 2cf1727a31c..3277e7ef6f7 100644
--- a/nixos/modules/security/wrappers/wrapper.c
+++ b/nixos/modules/security/wrappers/wrapper.c
@@ -17,14 +17,15 @@
 #include <syscall.h>
 #include <byteswap.h>
 
+// imported from glibc
+#include "unsecvars.h"
+
 #ifndef SOURCE_PROG
 #error SOURCE_PROG should be defined via preprocessor commandline
 #endif
 
 // aborts when false, printing the failed expression
 #define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
-// aborts when returns non-zero, printing the failed expression and errno
-#define MUSTSUCCEED(expr) ((expr) ? print_errno_and_die(#expr) : (void) 0)
 
 extern char **environ;
 
@@ -45,12 +46,6 @@ static noreturn void assert_failure(const char *assertion) {
     abort();
 }
 
-static noreturn void print_errno_and_die(const char *assertion) {
-    fprintf(stderr, "Call `%s` in NixOS's wrapper.c failed: %s\n", assertion, strerror(errno));
-    fflush(stderr);
-    abort();
-}
-
 int get_last_cap(unsigned *last_cap) {
     FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r");
     if (file == NULL) {
@@ -151,9 +146,55 @@ static int make_caps_ambient(const char *self_path) {
     return 0;
 }
 
+// These are environment variable aliases for glibc tunables.
+// This list shouldn't grow further, since this is a legacy mechanism.
+// Any future tunables are expected to only be accessible through GLIBC_TUNABLES.
+//
+// They are not included in the glibc-provided UNSECURE_ENVVARS list,
+// since any SUID executable ignores them. This wrapper also serves
+// executables that are merely granted ambient capabilities, rather than
+// being SUID, and hence don't run in secure mode. We'd like them to
+// defend those in depth as well, so we clear these explicitly.
+//
+// Except for MALLOC_CHECK_ (which is marked SXID_ERASE), these are all
+// marked SXID_IGNORE (ignored in secure mode), so even the glibc version
+// of this wrapper would leave them intact.
+#define UNSECURE_ENVVARS_TUNABLES \
+    "MALLOC_CHECK_\0" \
+    "MALLOC_TOP_PAD_\0" \
+    "MALLOC_PERTURB_\0" \
+    "MALLOC_MMAP_THRESHOLD_\0" \
+    "MALLOC_TRIM_THRESHOLD_\0" \
+    "MALLOC_MMAP_MAX_\0" \
+    "MALLOC_ARENA_MAX\0" \
+    "MALLOC_ARENA_TEST\0"
+
 int main(int argc, char **argv) {
     ASSERT(argc >= 1);
 
+    int debug = getenv(wrapper_debug) != NULL;
+
+    // Drop insecure environment variables explicitly
+    //
+    // glibc does this automatically in SUID binaries, but we'd like to cover this:
+    //
+    //  a) before it gets to glibc
+    //  b) in binaries that are only granted ambient capabilities by the wrapper,
+    //     but don't run with an altered effective UID/GID, nor directly gain
+    //     capabilities themselves, and thus don't run in secure mode.
+    //
+    // We're using musl, which doesn't drop environment variables in secure mode,
+    // and we'd also like glibc-specific variables to be covered.
+    //
+    // If we don't explicitly unset them, it's quite easy to just set LD_PRELOAD,
+    // have it passed through to the wrapped program, and gain privileges.
+    for (char *unsec = UNSECURE_ENVVARS_TUNABLES UNSECURE_ENVVARS; *unsec; unsec = strchr(unsec, 0) + 1) {
+        if (debug) {
+            fprintf(stderr, "unsetting %s\n", unsec);
+        }
+        unsetenv(unsec);
+    }
+
     // Read the capabilities set on the wrapper and raise them in to
     // the ambient set so the program we're wrapping receives the
     // capabilities too!
diff --git a/nixos/modules/security/wrappers/wrapper.nix b/nixos/modules/security/wrappers/wrapper.nix
index aec43412404..27d46c630af 100644
--- a/nixos/modules/security/wrappers/wrapper.nix
+++ b/nixos/modules/security/wrappers/wrapper.nix
@@ -1,11 +1,10 @@
-{ stdenv, linuxHeaders, sourceProg, debug ? false }:
+{ stdenv, unsecvars, linuxHeaders, sourceProg, debug ? false }:
 # For testing:
 # $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }'
 stdenv.mkDerivation {
   name = "security-wrapper";
   buildInputs = [ linuxHeaders ];
   dontUnpack = true;
-  hardeningEnable = [ "pie" ];
   CFLAGS = [
     ''-DSOURCE_PROG="${sourceProg}"''
   ] ++ (if debug then [
@@ -16,6 +15,6 @@ stdenv.mkDerivation {
   dontStrip = debug;
   installPhase = ''
     mkdir -p $out/bin
-    $CC $CFLAGS ${./wrapper.c} -o $out/bin/security-wrapper
+    $CC $CFLAGS ${./wrapper.c} -I${unsecvars} -o $out/bin/security-wrapper
   '';
 }
diff --git a/nixos/modules/services/audio/castopod.md b/nixos/modules/services/audio/castopod.md
new file mode 100644
index 00000000000..ee8590737a7
--- /dev/null
+++ b/nixos/modules/services/audio/castopod.md
@@ -0,0 +1,22 @@
+# Castopod {#module-services-castopod}
+
+Castopod is an open-source hosting platform made for podcasters who want to engage and interact with their audience.
+
+## Quickstart {#module-services-castopod-quickstart}
+
+Use the following configuration to start a public instance of Castopod on `castopod.example.com` domain:
+
+```nix
+networking.firewall.allowedTCPPorts = [ 80 443 ];
+services.castopod = {
+  enable = true;
+  database.createLocally = true;
+  nginx.virtualHost = {
+    serverName = "castopod.example.com";
+    enableACME = true;
+    forceSSL = true;
+  };
+};
+```
+
+Go to `https://castopod.example.com/cp-install` to create superadmin account after applying the above configuration.
diff --git a/nixos/modules/services/audio/castopod.nix b/nixos/modules/services/audio/castopod.nix
new file mode 100644
index 00000000000..b782b548914
--- /dev/null
+++ b/nixos/modules/services/audio/castopod.nix
@@ -0,0 +1,287 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.castopod;
+  fpm = config.services.phpfpm.pools.castopod;
+
+  user = "castopod";
+  stateDirectory = "/var/lib/castopod";
+
+  # https://docs.castopod.org/getting-started/install.html#requirements
+  phpPackage = pkgs.php.withExtensions ({ enabled, all }: with all; [
+    intl
+    curl
+    mbstring
+    gd
+    exif
+    mysqlnd
+  ] ++ enabled);
+in
+{
+  meta.doc = ./castopod.md;
+  meta.maintainers = with lib.maintainers; [ alexoundos misuzu ];
+
+  options.services = {
+    castopod = {
+      enable = lib.mkEnableOption (lib.mdDoc "Castopod");
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.castopod;
+        defaultText = lib.literalMD "pkgs.castopod";
+        description = lib.mdDoc "Which Castopod package to use.";
+      };
+      database = {
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Create the database and database user locally.
+          '';
+        };
+        hostname = lib.mkOption {
+          type = lib.types.str;
+          default = "localhost";
+          description = lib.mdDoc "Database hostname.";
+        };
+        name = lib.mkOption {
+          type = lib.types.str;
+          default = "castopod";
+          description = lib.mdDoc "Database name.";
+        };
+        user = lib.mkOption {
+          type = lib.types.str;
+          default = user;
+          description = lib.mdDoc "Database user.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/run/keys/castopod-dbpassword";
+          description = lib.mdDoc ''
+            A file containing the password corresponding to
+            [](#opt-services.castopod.database.user).
+          '';
+        };
+      };
+      settings = lib.mkOption {
+        type = with lib.types; attrsOf (oneOf [ str int bool ]);
+        default = { };
+        example = {
+          "email.protocol" = "smtp";
+          "email.SMTPHost" = "localhost";
+          "email.SMTPUser" = "myuser";
+          "email.fromEmail" = "castopod@example.com";
+        };
+        description = lib.mdDoc ''
+          Environment variables used for Castopod.
+          See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
+          for available environment variables.
+        '';
+      };
+      environmentFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/castopod-env";
+        description = lib.mdDoc ''
+          Environment file to inject e.g. secrets into the configuration.
+          See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
+          for available environment variables.
+        '';
+      };
+      configureNginx = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc "Configure nginx as a reverse proxy for CastoPod.";
+      };
+      localDomain = lib.mkOption {
+        type = lib.types.str;
+        example = "castopod.example.org";
+        description = lib.mdDoc "The domain serving your CastoPod instance.";
+      };
+      poolSettings = lib.mkOption {
+        type = with lib.types; attrsOf (oneOf [ str int bool ]);
+        default = {
+          "pm" = "dynamic";
+          "pm.max_children" = "32";
+          "pm.start_servers" = "2";
+          "pm.min_spare_servers" = "2";
+          "pm.max_spare_servers" = "4";
+          "pm.max_requests" = "500";
+        };
+        description = lib.mdDoc ''
+          Options for Castopod's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.castopod.settings =
+      let
+        sslEnabled = with config.services.nginx.virtualHosts.${cfg.localDomain}; addSSL || forceSSL || onlySSL || enableACME || useACMEHost != null;
+        baseURL = "http${lib.optionalString sslEnabled "s"}://${cfg.localDomain}";
+      in
+      lib.mapAttrs (name: lib.mkDefault) {
+        "app.forceGlobalSecureRequests" = sslEnabled;
+        "app.baseURL" = baseURL;
+
+        "media.baseURL" = "/";
+        "media.root" = "media";
+        "media.storage" = stateDirectory;
+
+        "admin.gateway" = "admin";
+        "auth.gateway" = "auth";
+
+        "database.default.hostname" = cfg.database.hostname;
+        "database.default.database" = cfg.database.name;
+        "database.default.username" = cfg.database.user;
+        "database.default.DBPrefix" = "cp_";
+
+        "cache.handler" = "file";
+      };
+
+    services.phpfpm.pools.castopod = {
+      inherit user;
+      group = config.services.nginx.group;
+      phpPackage = phpPackage;
+      phpOptions = ''
+        # https://code.castopod.org/adaures/castopod/-/blob/main/docker/production/app/uploads.ini
+        file_uploads = On
+        memory_limit = 512M
+        upload_max_filesize = 500M
+        post_max_size = 512M
+        max_execution_time = 300
+        max_input_time = 300
+      '';
+      settings = {
+        "listen.owner" = config.services.nginx.user;
+        "listen.group" = config.services.nginx.group;
+      } // cfg.poolSettings;
+    };
+
+    systemd.services.castopod-setup = {
+      after = lib.optional config.services.mysql.enable "mysql.service";
+      requires = lib.optional config.services.mysql.enable "mysql.service";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.openssl phpPackage ];
+      script =
+        let
+          envFile = "${stateDirectory}/.env";
+          media = "${cfg.settings."media.storage"}/${cfg.settings."media.root"}";
+        in
+        ''
+          mkdir -p ${stateDirectory}/writable/{cache,logs,session,temp,uploads}
+
+          if [ ! -d ${lib.escapeShellArg media} ]; then
+            cp --no-preserve=mode,ownership -r ${cfg.package}/share/castopod/public/media ${lib.escapeShellArg media}
+          fi
+
+          if [ ! -f ${stateDirectory}/salt ]; then
+            openssl rand -base64 33 > ${stateDirectory}/salt
+          fi
+
+          cat <<'EOF' > ${envFile}
+          ${lib.generators.toKeyValue { } cfg.settings}
+          EOF
+
+          echo "analytics.salt=$(cat ${stateDirectory}/salt)" >> ${envFile}
+
+          ${if (cfg.database.passwordFile != null) then ''
+            echo "database.default.password=$(cat ${lib.escapeShellArg cfg.database.passwordFile})" >> ${envFile}
+          '' else ''
+            echo "database.default.password=" >> ${envFile}
+          ''}
+
+          ${lib.optionalString (cfg.environmentFile != null) ''
+            cat ${lib.escapeShellArg cfg.environmentFile}) >> ${envFile}
+          ''}
+
+          php spark castopod:database-update
+        '';
+      serviceConfig = {
+        StateDirectory = "castopod";
+        WorkingDirectory = "${cfg.package}/share/castopod";
+        Type = "oneshot";
+        RemainAfterExit = true;
+        User = user;
+        Group = config.services.nginx.group;
+      };
+    };
+
+    systemd.services.castopod-scheduled = {
+      after = [ "castopod-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ phpPackage ];
+      script = ''
+        php public/index.php scheduled-activities
+        php public/index.php scheduled-websub-publish
+        php public/index.php scheduled-video-clips
+      '';
+      serviceConfig = {
+        StateDirectory = "castopod";
+        WorkingDirectory = "${cfg.package}/share/castopod";
+        Type = "oneshot";
+        User = user;
+        Group = config.services.nginx.group;
+      };
+    };
+
+    systemd.timers.castopod-scheduled = {
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "*-*-* *:*:00";
+        Unit = "castopod-scheduled.service";
+      };
+    };
+
+    services.mysql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      package = lib.mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [{
+        name = cfg.database.user;
+        ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+      }];
+    };
+
+    services.nginx = lib.mkIf cfg.configureNginx {
+      enable = true;
+      virtualHosts."${cfg.localDomain}" = {
+        root = lib.mkForce "${cfg.package}/share/castopod/public";
+
+        extraConfig = ''
+          try_files $uri $uri/ /index.php?$args;
+          index index.php index.html;
+        '';
+
+        locations."^~ /${cfg.settings."media.root"}/" = {
+          root = cfg.settings."media.storage";
+          extraConfig = ''
+            add_header Access-Control-Allow-Origin "*";
+            expires max;
+            access_log off;
+          '';
+        };
+
+        locations."~ \.php$" = {
+          fastcgiParams = {
+            SERVER_NAME = "$host";
+          };
+          extraConfig = ''
+            fastcgi_intercept_errors on;
+            fastcgi_index index.php;
+            fastcgi_pass unix:${fpm.socket};
+            try_files $uri =404;
+            fastcgi_read_timeout 3600;
+            fastcgi_send_timeout 3600;
+          '';
+        };
+      };
+    };
+
+    users.users.${user} = lib.mapAttrs (name: lib.mkDefault) {
+      description = "Castopod user";
+      isSystemUser = true;
+      group = config.services.nginx.group;
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/gonic.nix b/nixos/modules/services/audio/gonic.nix
index 65cf10f2c4b..66daeb60b50 100644
--- a/nixos/modules/services/audio/gonic.nix
+++ b/nixos/modules/services/audio/gonic.nix
@@ -57,6 +57,7 @@ in
         ReadWritePaths = "";
         BindReadOnlyPaths = [
           # gonic can access scrobbling services
+          "-/etc/resolv.conf"
           "-/etc/ssl/certs/ca-certificates.crt"
           builtins.storeDir
           cfg.settings.podcast-path
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
index 105e99cb2f5..b51f2a78c98 100644
--- a/nixos/modules/services/audio/jack.nix
+++ b/nixos/modules/services/audio/jack.nix
@@ -225,7 +225,7 @@ in {
         description = "JACK Audio system service user";
         isSystemUser = true;
       };
-      # http://jackaudio.org/faq/linux_rt_config.html
+      # https://jackaudio.org/faq/linux_rt_config.html
       security.pam.loginLimits = [
         { domain = "@jackaudio"; type = "-"; item = "rtprio"; value = "99"; }
         { domain = "@jackaudio"; type = "-"; item = "memlock"; value = "unlimited"; }
diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix
index e18e61eb6d4..77a0e74af9c 100644
--- a/nixos/modules/services/audio/navidrome.nix
+++ b/nixos/modules/services/audio/navidrome.nix
@@ -28,10 +28,17 @@ in {
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Whether to open the TCP port in the firewall";
+      };
     };
   };
 
   config = mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.settings.Port];
+
     systemd.services.navidrome = {
       description = "Navidrome Media Server";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/audio/slimserver.nix b/nixos/modules/services/audio/slimserver.nix
index 9fbc68b7136..cdd9d551c50 100644
--- a/nixos/modules/services/audio/slimserver.nix
+++ b/nixos/modules/services/audio/slimserver.nix
@@ -54,7 +54,7 @@ in {
       serviceConfig = {
         User = "slimserver";
         # Issue 40589: Disable broken image/video support (audio still works!)
-        ExecStart = "${cfg.package}/slimserver.pl --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
+        ExecStart = "${lib.getExe cfg.package} --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
       };
     };
 
diff --git a/nixos/modules/services/audio/wyoming/faster-whisper.nix b/nixos/modules/services/audio/wyoming/faster-whisper.nix
index 1fb67ecfe50..2d56acdc1b4 100644
--- a/nixos/modules/services/audio/wyoming/faster-whisper.nix
+++ b/nixos/modules/services/audio/wyoming/faster-whisper.nix
@@ -37,6 +37,9 @@ in
             enable = mkEnableOption (mdDoc "Wyoming faster-whisper server");
 
             model = mkOption {
+              # Intersection between available and referenced models here:
+              # https://github.com/rhasspy/models/releases/tag/v1.0
+              # https://github.com/rhasspy/rhasspy3/blob/wyoming-v1/programs/asr/faster-whisper/server/wyoming_faster_whisper/download.py#L17-L27
               type = enum [
                 "tiny"
                 "tiny-int8"
@@ -44,7 +47,6 @@ in
                 "base-int8"
                 "small"
                 "small-int8"
-                "medium"
                 "medium-int8"
               ];
               default = "tiny-int8";
@@ -136,6 +138,7 @@ in
               --data-dir $STATE_DIRECTORY \
               --download-dir $STATE_DIRECTORY \
               --uri ${options.uri} \
+              --device ${options.device} \
               --model ${options.model} \
               --language ${options.language} \
               --beam-size ${options.beamSize} ${options.extraArgs}
@@ -143,6 +146,8 @@ in
           CapabilityBoundingSet = "";
           DeviceAllow = if builtins.elem options.device [ "cuda" "auto" ] then [
             # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
+            # CUDA not working? Check DeviceAllow and PrivateDevices first!
+            "/dev/nvidia0"
             "/dev/nvidia1"
             "/dev/nvidia2"
             "/dev/nvidia3"
@@ -157,7 +162,6 @@ in
           DevicePolicy = "closed";
           LockPersonality = true;
           MemoryDenyWriteExecute = true;
-          PrivateDevices = true;
           PrivateUsers = true;
           ProtectHome = true;
           ProtectHostname = true;
diff --git a/nixos/modules/services/audio/wyoming/openwakeword.nix b/nixos/modules/services/audio/wyoming/openwakeword.nix
new file mode 100644
index 00000000000..987818246bd
--- /dev/null
+++ b/nixos/modules/services/audio/wyoming/openwakeword.nix
@@ -0,0 +1,163 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.wyoming.openwakeword;
+
+  inherit (lib)
+    concatStringsSep
+    concatMapStringsSep
+    escapeShellArgs
+    mkOption
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkPackageOptionMD
+    mkRemovedOptionModule
+    types
+    ;
+
+  inherit (builtins)
+    toString
+    ;
+
+in
+
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "wyoming" "openwakeword" "models" ] "Configuring models has been removed, they are now dynamically discovered and loaded at runtime")
+  ];
+
+  meta.buildDocsInSandbox = false;
+
+  options.services.wyoming.openwakeword = with types; {
+    enable = mkEnableOption (mdDoc "Wyoming openWakeWord server");
+
+    package = mkPackageOptionMD pkgs "wyoming-openwakeword" { };
+
+    uri = mkOption {
+      type = strMatching "^(tcp|unix)://.*$";
+      default = "tcp://0.0.0.0:10400";
+      example = "tcp://192.0.2.1:5000";
+      description = mdDoc ''
+        URI to bind the wyoming server to.
+      '';
+    };
+
+    customModelsDirectories = mkOption {
+      type = listOf types.path;
+      default = [];
+      description = lib.mdDoc ''
+        Paths to directories with custom wake word models (*.tflite model files).
+      '';
+    };
+
+    preloadModels = mkOption {
+      type = listOf str;
+      default = [
+        "ok_nabu"
+      ];
+      example = [
+        # wyoming_openwakeword/models/*.tflite
+        "alexa"
+        "hey_jarvis"
+        "hey_mycroft"
+        "hey_rhasspy"
+        "ok_nabu"
+      ];
+      description = mdDoc ''
+        List of wake word models to preload after startup.
+      '';
+    };
+
+    threshold = mkOption {
+      type = float;
+      default = 0.5;
+      description = mdDoc ''
+        Activation threshold (0-1), where higher means fewer activations.
+
+        See trigger level for the relationship between activations and
+        wake word detections.
+      '';
+      apply = toString;
+    };
+
+    triggerLevel = mkOption {
+      type = int;
+      default = 1;
+      description = mdDoc ''
+        Number of activations before a detection is registered.
+
+        A higher trigger level means fewer detections.
+      '';
+      apply = toString;
+    };
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [ ];
+      description = mdDoc ''
+        Extra arguments to pass to the server commandline.
+      '';
+      apply = escapeShellArgs;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services."wyoming-openwakeword" = {
+      description = "Wyoming openWakeWord server";
+      after = [
+        "network-online.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      serviceConfig = {
+        DynamicUser = true;
+        User = "wyoming-openwakeword";
+        # https://github.com/home-assistant/addons/blob/master/openwakeword/rootfs/etc/s6-overlay/s6-rc.d/openwakeword/run
+        ExecStart = concatStringsSep " " [
+          "${cfg.package}/bin/wyoming-openwakeword"
+          "--uri ${cfg.uri}"
+          (concatMapStringsSep " " (model: "--preload-model ${model}") cfg.preloadModels)
+          (concatMapStringsSep " " (dir: "--custom-model-dir ${toString dir}") cfg.customModelsDirectories)
+          "--threshold ${cfg.threshold}"
+          "--trigger-level ${cfg.triggerLevel}"
+          "${cfg.extraArgs}"
+        ];
+        CapabilityBoundingSet = "";
+        DeviceAllow = "";
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectProc = "invisible";
+        ProcSubset = "all"; # reads /proc/cpuinfo
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RuntimeDirectory = "wyoming-openwakeword";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix
index 0acbf1b3eab..5a75a46e525 100644
--- a/nixos/modules/services/backup/bacula.nix
+++ b/nixos/modules/services/backup/bacula.nix
@@ -15,16 +15,16 @@ let
       Client {
         Name = "${fd_cfg.name}";
         FDPort = ${toString fd_cfg.port};
-        WorkingDirectory = "${libDir}";
-        Pid Directory = "/run";
+        WorkingDirectory = ${libDir};
+        Pid Directory = /run;
         ${fd_cfg.extraClientConfig}
       }
 
       ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
       Director {
         Name = "${name}";
-        Password = "${value.password}";
-        Monitor = "${value.monitor}";
+        Password = ${value.password};
+        Monitor = ${value.monitor};
       }
       '') fd_cfg.director)}
 
@@ -41,8 +41,8 @@ let
       Storage {
         Name = "${sd_cfg.name}";
         SDPort = ${toString sd_cfg.port};
-        WorkingDirectory = "${libDir}";
-        Pid Directory = "/run";
+        WorkingDirectory = ${libDir};
+        Pid Directory = /run;
         ${sd_cfg.extraStorageConfig}
       }
 
@@ -50,8 +50,8 @@ let
       Autochanger {
         Name = "${name}";
         Device = ${concatStringsSep ", " (map (a: "\"${a}\"") value.devices)};
-        Changer Device =  "${value.changerDevice}";
-        Changer Command = "${value.changerCommand}";
+        Changer Device =  ${value.changerDevice};
+        Changer Command = ${value.changerCommand};
         ${value.extraAutochangerConfig}
       }
       '') sd_cfg.autochanger)}
@@ -59,8 +59,8 @@ let
       ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
       Device {
         Name = "${name}";
-        Archive Device = "${value.archiveDevice}";
-        Media Type = "${value.mediaType}";
+        Archive Device = ${value.archiveDevice};
+        Media Type = ${value.mediaType};
         ${value.extraDeviceConfig}
       }
       '') sd_cfg.device)}
@@ -68,8 +68,8 @@ let
       ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
       Director {
         Name = "${name}";
-        Password = "${value.password}";
-        Monitor = "${value.monitor}";
+        Password = ${value.password};
+        Monitor = ${value.monitor};
       }
       '') sd_cfg.director)}
 
@@ -85,18 +85,18 @@ let
     ''
     Director {
       Name = "${dir_cfg.name}";
-      Password = "${dir_cfg.password}";
+      Password = ${dir_cfg.password};
       DirPort = ${toString dir_cfg.port};
-      Working Directory = "${libDir}";
-      Pid Directory = "/run/";
-      QueryFile = "${pkgs.bacula}/etc/query.sql";
+      Working Directory = ${libDir};
+      Pid Directory = /run/;
+      QueryFile = ${pkgs.bacula}/etc/query.sql;
       ${dir_cfg.extraDirectorConfig}
     }
 
     Catalog {
-      Name = "PostgreSQL";
-      dbname = "bacula";
-      user = "bacula";
+      Name = PostgreSQL;
+      dbname = bacula;
+      user = bacula;
     }
 
     Messages {
@@ -533,7 +533,7 @@ in {
       };
     };
 
-    services.postgresql.enable = dir_cfg.enable == true;
+    services.postgresql.enable = lib.mkIf dir_cfg.enable true;
 
     systemd.services.bacula-dir = mkIf dir_cfg.enable {
       after = [ "network.target" "postgresql.service" ];
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 3b44f097ab7..28887f8e2ad 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -84,8 +84,8 @@ let
       backupScript = mkBackupScript backupJobName cfg;
     in nameValuePair backupJobName {
       description = "BorgBackup job ${name}";
-      path = with pkgs; [
-        borgbackup openssh
+      path =  [
+        config.services.borgbackup.package pkgs.openssh
       ];
       script = "exec " + optionalString cfg.inhibitsSleep ''\
         ${pkgs.systemd}/bin/systemd-inhibit \
@@ -137,7 +137,7 @@ let
     '');
 
   mkBorgWrapper = name: cfg: mkWrapperDrv {
-    original = "${pkgs.borgbackup}/bin/borg";
+    original = getExe config.services.borgbackup.package;
     name = "borg-job-${name}";
     set = { BORG_REPO = cfg.repo; } // (mkPassEnv cfg) // cfg.environment;
   };
@@ -231,6 +231,8 @@ in {
 
   ###### interface
 
+  options.services.borgbackup.package = mkPackageOptionMD pkgs "borgbackup" { };
+
   options.services.borgbackup.jobs = mkOption {
     description = lib.mdDoc ''
       Deduplicating backups using BorgBackup.
@@ -769,6 +771,7 @@ in {
 
       users = mkMerge (mapAttrsToList mkUsersConfig repos);
 
-      environment.systemPackages = with pkgs; [ borgbackup ] ++ (mapAttrsToList mkBorgWrapper jobs);
+      environment.systemPackages =
+        [ config.services.borgbackup.package ] ++ (mapAttrsToList mkBorgWrapper jobs);
     });
 }
diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix
index 5ee036e68c7..b27dd281712 100644
--- a/nixos/modules/services/backup/borgmatic.nix
+++ b/nixos/modules/services/backup/borgmatic.nix
@@ -6,32 +6,50 @@ let
   cfg = config.services.borgmatic;
   settingsFormat = pkgs.formats.yaml { };
 
+  repository = with types; submodule {
+    options = {
+      path = mkOption {
+        type = str;
+        description = mdDoc ''
+          Path to the repository
+        '';
+      };
+      label = mkOption {
+        type = str;
+        description = mdDoc ''
+          Label to the repository
+        '';
+      };
+    };
+  };
   cfgType = with types; submodule {
     freeformType = settingsFormat.type;
-    options.location = {
+    options = {
       source_directories = mkOption {
-        type = listOf str;
+        type = nullOr (listOf str);
+        default = null;
         description = mdDoc ''
-          List of source directories to backup (required). Globs and
-          tildes are expanded.
+          List of source directories and files to backup. Globs and tildes are
+          expanded. Do not backslash spaces in path names.
         '';
-        example = [ "/home" "/etc" "/var/log/syslog*" ];
+        example = [ "/home" "/etc" "/var/log/syslog*" "/home/user/path with spaces" ];
       };
       repositories = mkOption {
-        type = listOf str;
+        type = nullOr (listOf repository);
+        default = null;
         description = mdDoc ''
-          Paths to local or remote repositories (required). Tildes are
-          expanded. Multiple repositories are backed up to in
-          sequence. Borg placeholders can be used. See the output of
-          "borg help placeholders" for details. See ssh_command for
-          SSH options like identity file or port. If systemd service
-          is used, then add local repository paths in the systemd
-          service file to the ReadWritePaths list.
+          A required list of local or remote repositories with paths and
+          optional labels (which can be used with the --repository flag to
+          select a repository). Tildes are expanded. Multiple repositories are
+          backed up to in sequence. Borg placeholders can be used. See the
+          output of "borg help placeholders" for details. See ssh_command for
+          SSH options like identity file or port. If systemd service is used,
+          then add local repository paths in the systemd service file to the
+          ReadWritePaths list.
         '';
         example = [
-          "ssh://user@backupserver/./sourcehostname.borg"
-          "ssh://user@backupserver/./{fqdn}"
-          "/var/local/backups/local.borg"
+          { path="ssh://user@backupserver/./sourcehostname.borg"; label="backupserver"; }
+          { path="/mnt/backup"; label="local"; }
         ];
       };
     };
@@ -62,6 +80,13 @@ in
 
   config = mkIf cfg.enable {
 
+    warnings = []
+      ++ optional (cfg.settings != null && cfg.settings ? location)
+        "`services.borgmatic.settings.location` is deprecated, please move your options out of sections to the global scope"
+      ++ optional (catAttrs "location" (attrValues cfg.configurations) != [])
+        "`services.borgmatic.configurations.<name>.location` is deprecated, please move your options out of sections to the global scope"
+    ;
+
     environment.systemPackages = [ pkgs.borgmatic ];
 
     environment.etc = (optionalAttrs (cfg.settings != null) { "borgmatic/config.yaml".source = cfgfile; }) //
diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index b838c174553..9b7f1566eb1 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -166,7 +166,7 @@ in
             { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
             { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
             # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
-            { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; }
+            { command = "/run/current-system/sw/bin/btrfs"; options = [ "NOPASSWD" ]; }
             { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
             { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
             ];
@@ -182,7 +182,7 @@ in
             (doasCmdNoPass "${pkgs.coreutils}/bin/mkdir")
             (doasCmdNoPass "${pkgs.coreutils}/bin/readlink")
             # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
-            (doasCmdNoPass "/run/current-system/bin/btrfs")
+            (doasCmdNoPass "/run/current-system/sw/bin/btrfs")
             (doasCmdNoPass "/run/current-system/sw/bin/mkdir")
             (doasCmdNoPass "/run/current-system/sw/bin/readlink")
 
diff --git a/nixos/modules/services/backup/postgresql-wal-receiver.nix b/nixos/modules/services/backup/postgresql-wal-receiver.nix
index 01fd57f5c50..773dc0ba447 100644
--- a/nixos/modules/services/backup/postgresql-wal-receiver.nix
+++ b/nixos/modules/services/backup/postgresql-wal-receiver.nix
@@ -7,7 +7,7 @@ let
     options = {
       postgresqlPackage = mkOption {
         type = types.package;
-        example = literalExpression "pkgs.postgresql_11";
+        example = literalExpression "pkgs.postgresql_15";
         description = lib.mdDoc ''
           PostgreSQL package to use.
         '';
@@ -124,7 +124,7 @@ in {
         example = literalExpression ''
           {
             main = {
-              postgresqlPackage = pkgs.postgresql_11;
+              postgresqlPackage = pkgs.postgresql_15;
               directory = /mnt/pg_wal/main/;
               slot = "main_wal_receiver";
               connection = "postgresql://user@somehost";
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 78220e99c3d..87595f39796 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -23,25 +23,13 @@ in
 
         environmentFile = mkOption {
           type = with types; nullOr str;
-          # added on 2021-08-28, s3CredentialsFile should
-          # be removed in the future (+ remember the warning)
-          default = config.s3CredentialsFile;
+          default = null;
           description = lib.mdDoc ''
             file containing the credentials to access the repository, in the
             format of an EnvironmentFile as described by systemd.exec(5)
           '';
         };
 
-        s3CredentialsFile = mkOption {
-          type = with types; nullOr str;
-          default = null;
-          description = lib.mdDoc ''
-            file containing the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
-            for an S3-hosted repository, in the format of an EnvironmentFile
-            as described by systemd.exec(5)
-          '';
-        };
-
         rcloneOptions = mkOption {
           type = with types; nullOr (attrsOf (oneOf [ str bool ]));
           default = null;
@@ -113,12 +101,15 @@ in
         };
 
         paths = mkOption {
+          # This is nullable for legacy reasons only. We should consider making it a pure listOf
+          # after some time has passed since this comment was added.
           type = types.nullOr (types.listOf types.str);
-          default = null;
+          default = [ ];
           description = lib.mdDoc ''
-            Which paths to backup.  If null or an empty array, no
-            backup command will be run.  This can be used to create a
-            prune-only job.
+            Which paths to backup, in addition to ones specified via
+            `dynamicFilesFrom`.  If null or an empty array and
+            `dynamicFilesFrom` is also null, no backup command will be run.
+             This can be used to create a prune-only job.
           '';
           example = [
             "/var/lib/postgresql"
@@ -142,13 +133,15 @@ in
         };
 
         timerConfig = mkOption {
-          type = types.attrsOf unitOption;
+          type = types.nullOr (types.attrsOf unitOption);
           default = {
             OnCalendar = "daily";
             Persistent = true;
           };
           description = lib.mdDoc ''
-            When to run the backup. See {manpage}`systemd.timer(5)` for details.
+            When to run the backup. See {manpage}`systemd.timer(5)` for
+            details. If null no timer is created and the backup will only
+            run when explicitly started.
           '';
           example = {
             OnCalendar = "00:05";
@@ -231,7 +224,7 @@ in
           description = lib.mdDoc ''
             A script that produces a list of files to back up.  The
             results of this command are given to the '--files-from'
-            option.
+            option. The result is merged with paths specified via `paths`.
           '';
           example = "find /home/matt/git -type d -name .git";
         };
@@ -297,7 +290,6 @@ in
   };
 
   config = {
-    warnings = mapAttrsToList (n: v: "services.restic.backups.${n}.s3CredentialsFile is deprecated, please use services.restic.backups.${n}.environmentFile instead.") (filterAttrs (n: v: v.s3CredentialsFile != null) config.services.restic.backups);
     assertions = mapAttrsToList (n: v: {
       assertion = (v.repository == null) != (v.repositoryFile == null);
       message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
@@ -310,10 +302,7 @@ in
             resticCmd = "${backup.package}/bin/restic${extraOptions}";
             excludeFlags = optional (backup.exclude != []) "--exclude-file=${pkgs.writeText "exclude-patterns" (concatStringsSep "\n" backup.exclude)}";
             filesFromTmpFile = "/run/restic-backups-${name}/includes";
-            backupPaths =
-              if (backup.dynamicFilesFrom == null)
-              then optionalString (backup.paths != null) (concatStringsSep " " backup.paths)
-              else "--files-from ${filesFromTmpFile}";
+            doBackup = (backup.dynamicFilesFrom != null) || (backup.paths != null && backup.paths != []);
             pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
               (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts))
               (resticCmd + " check " + (concatStringsSep " " backup.checkOpts))
@@ -348,7 +337,7 @@ in
             after = [ "network-online.target" ];
             serviceConfig = {
               Type = "oneshot";
-              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ])
+              ExecStart = (optionals doBackup [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} --files-from=${filesFromTmpFile}" ])
                 ++ pruneCmd;
               User = backup.user;
               RuntimeDirectory = "restic-backups-${name}";
@@ -358,7 +347,7 @@ in
             } // optionalAttrs (backup.environmentFile != null) {
               EnvironmentFile = backup.environmentFile;
             };
-          } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null || backup.backupPrepareCommand != null) {
+          } // optionalAttrs (backup.initialize || doBackup || backup.backupPrepareCommand != null) {
             preStart = ''
               ${optionalString (backup.backupPrepareCommand != null) ''
                 ${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
@@ -366,16 +355,19 @@ in
               ${optionalString (backup.initialize) ''
                 ${resticCmd} snapshots || ${resticCmd} init
               ''}
+              ${optionalString (backup.paths != null && backup.paths != []) ''
+                cat ${pkgs.writeText "staticPaths" (concatStringsSep "\n" backup.paths)} >> ${filesFromTmpFile}
+              ''}
               ${optionalString (backup.dynamicFilesFrom != null) ''
-                ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile}
+                ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
               ''}
             '';
-          } // optionalAttrs (backup.dynamicFilesFrom != null || backup.backupCleanupCommand != null) {
+          } // optionalAttrs (doBackup || backup.backupCleanupCommand != null) {
             postStop = ''
               ${optionalString (backup.backupCleanupCommand != null) ''
                 ${pkgs.writeScript "backupCleanupCommand" backup.backupCleanupCommand}
               ''}
-              ${optionalString (backup.dynamicFilesFrom != null) ''
+              ${optionalString doBackup ''
                 rm ${filesFromTmpFile}
               ''}
             '';
@@ -388,7 +380,7 @@ in
           wantedBy = [ "timers.target" ];
           timerConfig = backup.timerConfig;
         })
-        config.services.restic.backups;
+        (filterAttrs (_: backup: backup.timerConfig != null) config.services.restic.backups);
 
     # generate wrapper scripts, as described in the createWrapper option
     environment.systemPackages = lib.mapAttrsToList (name: backup: let
diff --git a/nixos/modules/services/backup/syncoid.nix b/nixos/modules/services/backup/syncoid.nix
index 0f375455e7e..1a1df38617b 100644
--- a/nixos/modules/services/backup/syncoid.nix
+++ b/nixos/modules/services/backup/syncoid.nix
@@ -369,7 +369,7 @@ in
               PrivateDevices = true;
               PrivateMounts = true;
               PrivateNetwork = mkDefault false;
-              PrivateUsers = true;
+              PrivateUsers = false; # Enabling this breaks on zfs-2.2.0
               ProtectClock = true;
               ProtectControlGroups = true;
               ProtectHome = true;
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index 76f147c18af..2ebe8ad2f69 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -359,14 +359,14 @@ in
       };
 
       features.oracleMode = mkEnableOption (lib.mdDoc ''
-        Destroy snapshots one by one instead of using one long argument list.
+        destroying snapshots one by one instead of using one long argument list.
         If source and destination are out of sync for a long time, you may have
         so many snapshots to destroy that the argument gets is too long and the
-        command fails.
+        command fails
       '');
       features.recvu = mkEnableOption (lib.mdDoc ''
         recvu feature which uses `-u` on the receiving end to keep the destination
-        filesystem unmounted.
+        filesystem unmounted
       '');
       features.compressed = mkEnableOption (lib.mdDoc ''
         compressed feature which adds the options `-Lce` to
@@ -377,7 +377,7 @@ in
         support and -e is for embedded data support. see
         {manpage}`znapzend(1)`
         and {manpage}`zfs(8)`
-        for more info.
+        for more info
       '');
       features.sendRaw = mkEnableOption (lib.mdDoc ''
         sendRaw feature which adds the options `-w` to the
@@ -386,25 +386,25 @@ in
         backup that can't be read without the encryption key/passphrase, useful
         when the remote isn't fully trusted or not physically secure. This
         option must be used consistently, raw incrementals cannot be based on
-        non-raw snapshots and vice versa.
+        non-raw snapshots and vice versa
       '');
       features.skipIntermediates = mkEnableOption (lib.mdDoc ''
-        Enable the skipIntermediates feature to send a single increment
+        the skipIntermediates feature to send a single increment
         between latest common snapshot and the newly made one. It may skip
         several source snaps if the destination was offline for some time, and
         it should skip snapshots not managed by znapzend. Normally for online
         destinations, the new snapshot is sent as soon as it is created on the
-        source, so there are no automatic increments to skip.
+        source, so there are no automatic increments to skip
       '');
       features.lowmemRecurse = mkEnableOption (lib.mdDoc ''
         use lowmemRecurse on systems where you have too many datasets, so a
         recursive listing of attributes to find backup plans exhausts the
         memory available to {command}`znapzend`: instead, go the slower
         way to first list all impacted dataset names, and then query their
-        configs one by one.
+        configs one by one
       '');
       features.zfsGetType = mkEnableOption (lib.mdDoc ''
-        use zfsGetType if your {command}`zfs get` supports a
+        using zfsGetType if your {command}`zfs get` supports a
         `-t` argument for filtering by dataset type at all AND
         lists properties for snapshots by default when recursing, so that there
         is too much data to process while searching for backup plans.
@@ -412,7 +412,7 @@ in
         `--recursive` search for backup plans can literally
         differ by hundreds of times (depending on the amount of snapshots in
         that dataset tree... and a decent backup plan will ensure you have a lot
-        of those), so you would benefit from requesting this feature.
+        of those), so you would benefit from requesting this feature
       '');
     };
   };
diff --git a/nixos/modules/services/blockchain/ethereum/erigon.nix b/nixos/modules/services/blockchain/ethereum/erigon.nix
index 8ebe0fcaff5..945a373d127 100644
--- a/nixos/modules/services/blockchain/ethereum/erigon.nix
+++ b/nixos/modules/services/blockchain/ethereum/erigon.nix
@@ -13,6 +13,8 @@ in {
     services.erigon = {
       enable = mkEnableOption (lib.mdDoc "Ethereum implementation on the efficiency frontier");
 
+      package = mkPackageOptionMD pkgs "erigon" { };
+
       extraArgs = mkOption {
         type = types.listOf types.str;
         description = lib.mdDoc "Additional arguments passed to Erigon";
@@ -92,7 +94,7 @@ in {
 
       serviceConfig = {
         LoadCredential = "ERIGON_JWT:${cfg.secretJwtPath}";
-        ExecStart = "${pkgs.erigon}/bin/erigon --config ${configFile} --authrpc.jwtsecret=%d/ERIGON_JWT ${lib.escapeShellArgs cfg.extraArgs}";
+        ExecStart = "${cfg.package}/bin/erigon --config ${configFile} --authrpc.jwtsecret=%d/ERIGON_JWT ${lib.escapeShellArgs cfg.extraArgs}";
         DynamicUser = true;
         Restart = "on-failure";
         StateDirectory = "erigon";
diff --git a/nixos/modules/services/cluster/hadoop/default.nix b/nixos/modules/services/cluster/hadoop/default.nix
index 72bf25c2114..ff6b4d5588b 100644
--- a/nixos/modules/services/cluster/hadoop/default.nix
+++ b/nixos/modules/services/cluster/hadoop/default.nix
@@ -67,16 +67,16 @@ with lib;
     mapredSiteDefault = mkOption {
       default = {
         "mapreduce.framework.name" = "yarn";
-        "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
-        "mapreduce.map.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
-        "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
+        "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=${cfg.package}";
+        "mapreduce.map.env" = "HADOOP_MAPRED_HOME=${cfg.package}";
+        "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=${cfg.package}";
       };
       defaultText = literalExpression ''
         {
           "mapreduce.framework.name" = "yarn";
-          "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
-          "mapreduce.map.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
-          "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
+          "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}";
+          "mapreduce.map.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}";
+          "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}";
         }
       '';
       type = types.attrsOf types.anything;
@@ -154,13 +154,13 @@ with lib;
     };
 
     log4jProperties = mkOption {
-      default = "${cfg.package}/lib/${cfg.package.untarDir}/etc/hadoop/log4j.properties";
+      default = "${cfg.package}/etc/hadoop/log4j.properties";
       defaultText = literalExpression ''
-        "''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}/etc/hadoop/log4j.properties"
+        "''${config.${opt.package}}/etc/hadoop/log4j.properties"
       '';
       type = types.path;
       example = literalExpression ''
-        "''${pkgs.hadoop}/lib/''${pkgs.hadoop.untarDir}/etc/hadoop/log4j.properties";
+        "''${pkgs.hadoop}/etc/hadoop/log4j.properties";
       '';
       description = lib.mdDoc "log4j.properties file added to HADOOP_CONF_DIR";
     };
diff --git a/nixos/modules/services/cluster/hadoop/yarn.nix b/nixos/modules/services/cluster/hadoop/yarn.nix
index 26077f35fdd..a49aafbd1dc 100644
--- a/nixos/modules/services/cluster/hadoop/yarn.nix
+++ b/nixos/modules/services/cluster/hadoop/yarn.nix
@@ -160,7 +160,7 @@ in
           umount /run/wrappers/yarn-nodemanager/cgroup/cpu || true
           rm -rf /run/wrappers/yarn-nodemanager/ || true
           mkdir -p /run/wrappers/yarn-nodemanager/{bin,etc/hadoop,cgroup/cpu}
-          cp ${cfg.package}/lib/${cfg.package.untarDir}/bin/container-executor /run/wrappers/yarn-nodemanager/bin/
+          cp ${cfg.package}/bin/container-executor /run/wrappers/yarn-nodemanager/bin/
           chgrp hadoop /run/wrappers/yarn-nodemanager/bin/container-executor
           chmod 6050 /run/wrappers/yarn-nodemanager/bin/container-executor
           cp ${hadoopConf}/container-executor.cfg /run/wrappers/yarn-nodemanager/etc/hadoop/
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
index 51475171bf3..ff16795c820 100644
--- a/nixos/modules/services/computing/boinc/client.nix
+++ b/nixos/modules/services/computing/boinc/client.nix
@@ -54,7 +54,7 @@ in
           only the hosts listed in {var}`dataDir`/remote_hosts.cfg will be allowed to
           connect.
 
-          See also: <http://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access>
+          See also: <https://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access>
         '';
       };
 
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 344c43a429b..1cbe7b893f8 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.slurm;
   opt = options.services.slurm;
-  # configuration file can be generated by http://slurm.schedmd.com/configurator.html
+  # configuration file can be generated by https://slurm.schedmd.com/configurator.html
 
   defaultUser = "slurm";
 
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index b4b997201c8..9a89745055f 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -15,6 +15,7 @@ let
 
   defaultMasterCfg = pkgs.writeText "master.cfg" ''
     from buildbot.plugins import *
+    ${cfg.extraImports}
     factory = util.BuildFactory()
     c = BuildmasterConfig = dict(
      workers       = [${concatStringsSep "," cfg.workers}],
@@ -28,6 +29,7 @@ let
      schedulers    = [ ${concatStringsSep "," cfg.schedulers} ],
      builders      = [ ${concatStringsSep "," cfg.builders} ],
      services      = [ ${concatStringsSep "," cfg.reporters} ],
+     configurators = [ ${concatStringsSep "," cfg.configurators} ],
     )
     for step in [ ${concatStringsSep "," cfg.factorySteps} ]:
       factory.addStep(step)
@@ -79,6 +81,15 @@ in {
         ];
       };
 
+      configurators = mkOption {
+        type = types.listOf types.str;
+        description = lib.mdDoc "Configurator Steps, see https://docs.buildbot.net/latest/manual/configuration/configurators.html";
+        default = [];
+        example = [
+          "util.JanitorConfigurator(logHorizon=timedelta(weeks=4), hour=12, dayOfWeek=6)"
+        ];
+      };
+
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -91,6 +102,13 @@ in {
         default = "c['buildbotNetUsageData'] = None";
       };
 
+      extraImports = mkOption {
+        type = types.str;
+        description = lib.mdDoc "Extra python imports to prepend to master.cfg";
+        default = "";
+        example = "from buildbot.process.project import Project";
+      };
+
       masterCfg = mkOption {
         type = types.path;
         description = lib.mdDoc "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
diff --git a/nixos/modules/services/continuous-integration/woodpecker/server.nix b/nixos/modules/services/continuous-integration/woodpecker/server.nix
index cae5ed7cf11..38b42f7288c 100644
--- a/nixos/modules/services/continuous-integration/woodpecker/server.nix
+++ b/nixos/modules/services/continuous-integration/woodpecker/server.nix
@@ -31,9 +31,9 @@ in
         description = lib.mdDoc "woodpecker-server config environment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/server-config)";
       };
       environmentFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/root/woodpecker-server.env";
+        type = with lib.types; coercedTo path (f: [ f ]) (listOf path);
+        default = [ ];
+        example = [ "/root/woodpecker-server.env" ];
         description = lib.mdDoc ''
           File to load environment variables
           from. This is helpful for specifying secrets.
@@ -61,7 +61,7 @@ in
           StateDirectoryMode = "0700";
           UMask = "0007";
           ConfigurationDirectory = "woodpecker-server";
-          EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
+          EnvironmentFile = cfg.environmentFile;
           ExecStart = "${cfg.package}/bin/woodpecker-server";
           Restart = "on-failure";
           RestartSec = 15;
diff --git a/nixos/modules/services/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index e26acb88d8c..cd816ffaf0d 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -122,7 +122,7 @@ in
   options.services.cassandra = {
 
     enable = mkEnableOption (lib.mdDoc ''
-      Apache Cassandra – Scalable and highly available database.
+      Apache Cassandra – Scalable and highly available database
     '');
 
     clusterName = mkOption {
diff --git a/nixos/modules/services/databases/couchdb.nix b/nixos/modules/services/databases/couchdb.nix
index 0a81a8dceee..bfecfbb3664 100644
--- a/nixos/modules/services/databases/couchdb.nix
+++ b/nixos/modules/services/databases/couchdb.nix
@@ -79,7 +79,7 @@ in {
         '';
       };
 
-      # couchdb options: http://docs.couchdb.org/en/latest/config/index.html
+      # couchdb options: https://docs.couchdb.org/en/latest/config/index.html
 
       databaseDir = mkOption {
         type = types.path;
diff --git a/nixos/modules/services/databases/ferretdb.nix b/nixos/modules/services/databases/ferretdb.nix
new file mode 100644
index 00000000000..ab55e22bf21
--- /dev/null
+++ b/nixos/modules/services/databases/ferretdb.nix
@@ -0,0 +1,79 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ferretdb;
+in
+{
+
+  meta.maintainers = with lib.maintainers; [ julienmalka camillemndn ];
+
+  options = {
+    services.ferretdb = {
+      enable = mkEnableOption "FerretDB, an Open Source MongoDB alternative";
+
+      package = mkOption {
+        type = types.package;
+        example = literalExpression "pkgs.ferretdb";
+        default = pkgs.ferretdb;
+        defaultText = "pkgs.ferretdb";
+        description = "FerretDB package to use.";
+      };
+
+      settings = lib.mkOption {
+        type =
+          lib.types.submodule { freeformType = with lib.types; attrsOf str; };
+        example = {
+          FERRETDB_LOG_LEVEL = "warn";
+          FERRETDB_MODE = "normal";
+        };
+        description = ''
+          Additional configuration for FerretDB, see
+          <https://docs.ferretdb.io/configuration/flags/>
+          for supported values.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable
+    {
+
+      services.ferretdb.settings = {
+        FERRETDB_HANDLER = lib.mkDefault "sqlite";
+        FERRETDB_SQLITE_URL = lib.mkDefault "file:/var/lib/ferretdb/";
+      };
+
+      systemd.services.ferretdb = {
+        description = "FerretDB";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        environment = cfg.settings;
+        serviceConfig = {
+          Type = "simple";
+          StateDirectory = "ferretdb";
+          WorkingDirectory = "/var/lib/ferretdb";
+          ExecStart = "${cfg.package}/bin/ferretdb";
+          Restart = "on-failure";
+          ProtectHome = true;
+          ProtectSystem = "strict";
+          PrivateTmp = true;
+          PrivateDevices = true;
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          NoNewPrivileges = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          RemoveIPC = true;
+          PrivateMounts = true;
+          DynamicUser = true;
+        };
+      };
+    };
+}
+
diff --git a/nixos/modules/services/databases/firebird.nix b/nixos/modules/services/databases/firebird.nix
index 26ed46f0e60..3927c81d953 100644
--- a/nixos/modules/services/databases/firebird.nix
+++ b/nixos/modules/services/databases/firebird.nix
@@ -17,7 +17,7 @@
 # There are at least two ways to run firebird. superserver has been chosen
 # however there are no strong reasons to prefer this or the other one AFAIK
 # Eg superserver is said to be most efficiently using resources according to
-# http://www.firebirdsql.org/manual/qsg25-classic-or-super.html
+# https://www.firebirdsql.org/manual/qsg25-classic-or-super.html
 
 with lib;
 
diff --git a/nixos/modules/services/databases/pgmanage.nix b/nixos/modules/services/databases/pgmanage.nix
index cbf988d596f..a0933a5ffc4 100644
--- a/nixos/modules/services/databases/pgmanage.nix
+++ b/nixos/modules/services/databases/pgmanage.nix
@@ -66,7 +66,7 @@ in {
         pgmanage requires at least one PostgreSQL server be defined.
 
         Detailed information about PostgreSQL connection strings is available at:
-        <http://www.postgresql.org/docs/current/static/libpq-connect.html>
+        <https://www.postgresql.org/docs/current/libpq-connect.html>
 
         Note that you should not specify your user name or password. That
         information will be entered on the login screen. If you specify a
@@ -187,7 +187,7 @@ in {
       serviceConfig = {
         User         = pgmanage;
         Group        = pgmanage;
-        ExecStart    = "${pkgs.pgmanage}/sbin/pgmanage -c ${confFile}" +
+        ExecStart    = "${cfg.package}/sbin/pgmanage -c ${confFile}" +
                        optionalString cfg.localOnly " --local-only=true";
       };
     };
diff --git a/nixos/modules/services/databases/postgresql.md b/nixos/modules/services/databases/postgresql.md
index 4d66ee38be4..e5e0b7efec2 100644
--- a/nixos/modules/services/databases/postgresql.md
+++ b/nixos/modules/services/databases/postgresql.md
@@ -5,7 +5,7 @@
 
 *Source:* {file}`modules/services/databases/postgresql.nix`
 
-*Upstream documentation:* <http://www.postgresql.org/docs/>
+*Upstream documentation:* <https://www.postgresql.org/docs/>
 
 <!-- FIXME: more stuff, like maintainer? -->
 
@@ -17,9 +17,9 @@ PostgreSQL is an advanced, free relational database.
 To enable PostgreSQL, add the following to your {file}`configuration.nix`:
 ```
 services.postgresql.enable = true;
-services.postgresql.package = pkgs.postgresql_11;
+services.postgresql.package = pkgs.postgresql_15;
 ```
-Note that you are required to specify the desired version of PostgreSQL (e.g. `pkgs.postgresql_11`). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for [](#opt-services.postgresql.package) such as the most recent release of PostgreSQL.
+Note that you are required to specify the desired version of PostgreSQL (e.g. `pkgs.postgresql_15`). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for [](#opt-services.postgresql.package) such as the most recent release of PostgreSQL.
 
 <!--
 After running {command}`nixos-rebuild`, you can verify
@@ -39,6 +39,125 @@ By default, PostgreSQL stores its databases in {file}`/var/lib/postgresql/$psqlS
 services.postgresql.dataDir = "/data/postgresql";
 ```
 
+## Initializing {#module-services-postgres-initializing}
+
+As of NixOS 23.11,
+`services.postgresql.ensureUsers.*.ensurePermissions` has been
+deprecated, after a change to default permissions in PostgreSQL 15
+invalidated most of its previous use cases:
+
+- In psql < 15, `ALL PRIVILEGES` used to include `CREATE TABLE`, where
+  in psql >= 15 that would be a separate permission
+- psql >= 15 instead gives only the database owner create permissions
+- Even on psql < 15 (or databases migrated to >= 15), it is
+  recommended to manually assign permissions along these lines
+  - https://www.postgresql.org/docs/release/15.0/
+  - https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PRIV
+
+### Assigning ownership {#module-services-postgres-initializing-ownership}
+
+Usually, the database owner should be a database user of the same
+name. This can be done with
+`services.postgresql.ensureUsers.*.ensureDBOwnership = true;`.
+
+If the database user name equals the connecting system user name,
+postgres by default will accept a passwordless connection via unix
+domain socket. This makes it possible to run many postgres-backed
+services without creating any database secrets at all
+
+### Assigning extra permissions {#module-services-postgres-initializing-extra-permissions}
+
+For many cases, it will be enough to have the database user be the
+owner. Until `services.postgresql.ensureUsers.*.ensurePermissions` has
+been re-thought, if more users need access to the database, please use
+one of the following approaches:
+
+**WARNING:** `services.postgresql.initialScript` is not recommended
+for `ensurePermissions` replacement, as that is *only run on first
+start of PostgreSQL*.
+
+**NOTE:** all of these methods may be obsoleted, when `ensure*` is
+reworked, but it is expected that they will stay viable for running
+database migrations.
+
+**NOTE:** please make sure that any added migrations are idempotent (re-runnable).
+
+#### as superuser {#module-services-postgres-initializing-extra-permissions-superuser}
+
+**Advantage:** compatible with postgres < 15, because it's run
+as the database superuser `postgres`.
+
+##### in database `postStart` {#module-services-postgres-initializing-extra-permissions-superuser-post-start}
+
+**Disadvantage:** need to take care of ordering yourself. In this
+example, `mkAfter` ensures that permissions are assigned after any
+databases from `ensureDatabases` and `extraUser1` from `ensureUsers`
+are already created.
+
+```nix
+    systemd.services.postgresql.postStart = lib.mkAfter ''
+      $PSQL service1 -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+      $PSQL service1 -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+      # ....
+    '';
+```
+
+##### in intermediate oneshot service {#module-services-postgres-initializing-extra-permissions-superuser-oneshot}
+
+```nix
+    systemd.services."migrate-service1-db1" = {
+      serviceConfig.Type = "oneshot";
+      requiredBy = "service1.service";
+      before = "service1.service";
+      after = "postgresql.service";
+      serviceConfig.User = "postgres";
+      environment.PSQL = "psql --port=${toString services.postgresql.port}";
+      path = [ postgresql ];
+      script = ''
+        $PSQL service1 -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+        $PSQL service1 -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+        # ....
+      '';
+    };
+```
+
+#### as service user {#module-services-postgres-initializing-extra-permissions-service-user}
+
+**Advantage:** re-uses systemd's dependency ordering;
+
+**Disadvantage:** relies on service user having grant permission. To be combined with `ensureDBOwnership`.
+
+##### in service `preStart` {#module-services-postgres-initializing-extra-permissions-service-user-pre-start}
+
+```nix
+    environment.PSQL = "psql --port=${toString services.postgresql.port}";
+    path = [ postgresql ];
+    systemd.services."service1".preStart = ''
+      $PSQL -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+      $PSQL -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+      # ....
+    '';
+```
+
+##### in intermediate oneshot service {#module-services-postgres-initializing-extra-permissions-service-user-oneshot}
+
+```nix
+    systemd.services."migrate-service1-db1" = {
+      serviceConfig.Type = "oneshot";
+      requiredBy = "service1.service";
+      before = "service1.service";
+      after = "postgresql.service";
+      serviceConfig.User = "service1";
+      environment.PSQL = "psql --port=${toString services.postgresql.port}";
+      path = [ postgresql ];
+      script = ''
+        $PSQL -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+        $PSQL -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+        # ....
+      '';
+    };
+```
+
 ## Upgrading {#module-services-postgres-upgrading}
 
 ::: {.note}
@@ -119,27 +238,27 @@ A complete list of options for the PostgreSQL module may be found [here](#opt-se
 
 ## Plugins {#module-services-postgres-plugins}
 
-Plugins collection for each PostgreSQL version can be accessed with `.pkgs`. For example, for `pkgs.postgresql_11` package, its plugin collection is accessed by `pkgs.postgresql_11.pkgs`:
+Plugins collection for each PostgreSQL version can be accessed with `.pkgs`. For example, for `pkgs.postgresql_15` package, its plugin collection is accessed by `pkgs.postgresql_15.pkgs`:
 ```ShellSession
 $ nix repl '<nixpkgs>'
 
 Loading '<nixpkgs>'...
 Added 10574 variables.
 
-nix-repl> postgresql_11.pkgs.<TAB><TAB>
-postgresql_11.pkgs.cstore_fdw        postgresql_11.pkgs.pg_repack
-postgresql_11.pkgs.pg_auto_failover  postgresql_11.pkgs.pg_safeupdate
-postgresql_11.pkgs.pg_bigm           postgresql_11.pkgs.pg_similarity
-postgresql_11.pkgs.pg_cron           postgresql_11.pkgs.pg_topn
-postgresql_11.pkgs.pg_hll            postgresql_11.pkgs.pgjwt
-postgresql_11.pkgs.pg_partman        postgresql_11.pkgs.pgroonga
+nix-repl> postgresql_15.pkgs.<TAB><TAB>
+postgresql_15.pkgs.cstore_fdw        postgresql_15.pkgs.pg_repack
+postgresql_15.pkgs.pg_auto_failover  postgresql_15.pkgs.pg_safeupdate
+postgresql_15.pkgs.pg_bigm           postgresql_15.pkgs.pg_similarity
+postgresql_15.pkgs.pg_cron           postgresql_15.pkgs.pg_topn
+postgresql_15.pkgs.pg_hll            postgresql_15.pkgs.pgjwt
+postgresql_15.pkgs.pg_partman        postgresql_15.pkgs.pgroonga
 ...
 ```
 
 To add plugins via NixOS configuration, set `services.postgresql.extraPlugins`:
 ```
-services.postgresql.package = pkgs.postgresql_11;
-services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [
+services.postgresql.package = pkgs.postgresql_12;
+services.postgresql.extraPlugins = with pkgs.postgresql_12.pkgs; [
   pg_repack
   postgis
 ];
@@ -148,7 +267,7 @@ services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [
 You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using function `.withPackages`. For example, creating a custom PostgreSQL package in an overlay can look like:
 ```
 self: super: {
-  postgresql_custom = self.postgresql_11.withPackages (ps: [
+  postgresql_custom = self.postgresql_12.withPackages (ps: [
     ps.pg_repack
     ps.postgis
   ]);
@@ -158,9 +277,9 @@ self: super: {
 Here's a recipe on how to override a particular plugin through an overlay:
 ```
 self: super: {
-  postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // {
-    pkgs = super.postgresql_11.pkgs // {
-      pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: {
+  postgresql_15 = super.postgresql_15.override { this = self.postgresql_15; } // {
+    pkgs = super.postgresql_15.pkgs // {
+      pg_repack = super.postgresql_15.pkgs.pg_repack.overrideAttrs (_: {
         name = "pg_repack-v20181024";
         src = self.fetchzip {
           url = "https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz";
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 0acaf0fd00a..a9067d5974a 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -55,7 +55,7 @@ in
 
       package = mkOption {
         type = types.package;
-        example = literalExpression "pkgs.postgresql_11";
+        example = literalExpression "pkgs.postgresql_15";
         description = lib.mdDoc ''
           PostgreSQL package to use.
         '';
@@ -78,7 +78,7 @@ in
       dataDir = mkOption {
         type = types.path;
         defaultText = literalExpression ''"/var/lib/postgresql/''${config.services.postgresql.package.psqlSchema}"'';
-        example = "/var/lib/postgresql/11";
+        example = "/var/lib/postgresql/15";
         description = lib.mdDoc ''
           The data directory for PostgreSQL. If left as the default value
           this directory will automatically be created before the PostgreSQL server starts, otherwise
@@ -106,12 +106,14 @@ in
       identMap = mkOption {
         type = types.lines;
         default = "";
+        example = ''
+          map-name-0 system-username-0 database-username-0
+          map-name-1 system-username-1 database-username-1
+        '';
         description = lib.mdDoc ''
           Defines the mapping from system users to database users.
 
-          The general form is:
-
-          map-name system-username database-username
+          See the [auth doc](https://postgresql.org/docs/current/auth-username-maps.html).
         '';
       };
 
@@ -128,6 +130,11 @@ in
       initialScript = mkOption {
         type = types.nullOr types.path;
         default = null;
+        example = literalExpression ''
+          pkgs.writeText "init-sql-script" '''
+            alter user postgres with password 'myPassword';
+          ''';'';
+
         description = lib.mdDoc ''
           A file containing SQL statements to execute on first startup.
         '';
@@ -161,7 +168,12 @@ in
             ensurePermissions = mkOption {
               type = types.attrsOf types.str;
               default = {};
+              visible = false; # This option has been deprecated.
               description = lib.mdDoc ''
+                This option is DEPRECATED and should not be used in nixpkgs anymore,
+                use `ensureDBOwnership` instead. It can also break with newer
+                versions of PostgreSQL (≥ 15).
+
                 Permissions to ensure for the user, specified as an attribute set.
                 The attribute names specify the database and tables to grant the permissions for.
                 The attribute values specify the permissions to grant. You may specify one or
@@ -180,6 +192,16 @@ in
               '';
             };
 
+            ensureDBOwnership = mkOption {
+              type = types.bool;
+              default = false;
+              description = mdDoc ''
+                Grants the user ownership to a database with the same name.
+                This database must be defined manually in
+                [](#opt-services.postgresql.ensureDatabases).
+              '';
+            };
+
             ensureClauses = mkOption {
               description = lib.mdDoc ''
                 An attrset of clauses to grant to the user. Under the hood this uses the
@@ -331,26 +353,21 @@ in
         });
         default = [];
         description = lib.mdDoc ''
-          Ensures that the specified users exist and have at least the ensured permissions.
+          Ensures that the specified users exist.
           The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the
           same name only, and that without the need for a password.
-          This option will never delete existing users or remove permissions, especially not when the value of this
-          option is changed. This means that users created and permissions assigned once through this option or
-          otherwise have to be removed manually.
+          This option will never delete existing users or remove DB ownership of databases
+          once granted with `ensureDBOwnership = true;`. This means that this must be
+          cleaned up manually when changing after changing the config in here.
         '';
         example = literalExpression ''
           [
             {
               name = "nextcloud";
-              ensurePermissions = {
-                "DATABASE nextcloud" = "ALL PRIVILEGES";
-              };
             }
             {
               name = "superuser";
-              ensurePermissions = {
-                "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
-              };
+              ensureDBOwnership = true;
             }
           ]
         '';
@@ -380,7 +397,7 @@ in
       extraPlugins = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExpression "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
+        example = literalExpression "with pkgs.postgresql_15.pkgs; [ postgis pg_repack ]";
         description = lib.mdDoc ''
           List of PostgreSQL plugins. PostgreSQL version for each plugin should
           match version for `services.postgresql.package` value.
@@ -392,7 +409,7 @@ in
         default = {};
         description = lib.mdDoc ''
           PostgreSQL configuration. Refer to
-          <https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
+          <https://www.postgresql.org/docs/15/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
           for an overview of `postgresql.conf`.
 
           ::: {.note}
@@ -438,6 +455,27 @@ in
 
   config = mkIf cfg.enable {
 
+    assertions = map ({ name, ensureDBOwnership, ... }: {
+      assertion = ensureDBOwnership -> builtins.elem name cfg.ensureDatabases;
+      message = ''
+        For each database user defined with `services.postgresql.ensureUsers` and
+        `ensureDBOwnership = true;`, a database with the same name must be defined
+        in `services.postgresql.ensureDatabases`.
+
+        Offender: ${name} has not been found among databases.
+      '';
+    }) cfg.ensureUsers;
+    # `ensurePermissions` is now deprecated, let's avoid it.
+    warnings = lib.optional (any ({ ensurePermissions, ... }: ensurePermissions != {}) cfg.ensureUsers) "
+      `services.postgresql.*.ensurePermissions` is used in your expressions,
+      this option is known to be broken with newer PostgreSQL versions,
+      consider migrating to `services.postgresql.*.ensureDBOwnership` or
+      consult the release notes or manual for more migration guidelines.
+
+      This option will be removed in NixOS 24.05 unless it sees significant
+      maintenance improvements.
+    ";
+
     services.postgresql.settings =
       {
         hba_file = "${pkgs.writeText "pg_hba.conf" cfg.authentication}";
@@ -451,9 +489,10 @@ in
 
     services.postgresql.package = let
         mkThrow = ver: throw "postgresql_${ver} was removed, please upgrade your postgresql version.";
-        base = if versionAtLeast config.system.stateVersion "22.05" then pkgs.postgresql_14
+        base = if versionAtLeast config.system.stateVersion "23.11" then pkgs.postgresql_15
+            else if versionAtLeast config.system.stateVersion "22.05" then pkgs.postgresql_14
             else if versionAtLeast config.system.stateVersion "21.11" then pkgs.postgresql_13
-            else if versionAtLeast config.system.stateVersion "20.03" then pkgs.postgresql_11
+            else if versionAtLeast config.system.stateVersion "20.03" then mkThrow "11"
             else if versionAtLeast config.system.stateVersion "17.09" then mkThrow "9_6"
             else mkThrow "9_5";
     in
@@ -464,13 +503,16 @@ in
 
     services.postgresql.dataDir = mkDefault "/var/lib/postgresql/${cfg.package.psqlSchema}";
 
-    services.postgresql.authentication = mkAfter
+    services.postgresql.authentication = mkMerge [
+      (mkBefore "# Generated file; do not edit!")
+      (mkAfter
       ''
-        # Generated file; do not edit!
+        # default value of services.postgresql.authentication
         local all all              peer
         host  all all 127.0.0.1/32 md5
         host  all all ::1/128      md5
-      '';
+      '')
+    ];
 
     users.users.postgres =
       { name = "postgres";
@@ -545,12 +587,15 @@ in
             ${
               concatMapStrings
               (user:
-                let
+              let
                   userPermissions = concatStringsSep "\n"
                     (mapAttrsToList
                       (database: permission: ''$PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"' '')
                       user.ensurePermissions
                     );
+                  dbOwnershipStmt = optionalString
+                    user.ensureDBOwnership
+                    ''$PSQL -tAc 'ALTER DATABASE "${user.name}" OWNER TO "${user.name}";' '';
 
                   filteredClauses = filterAttrs (name: value: value != null) user.ensureClauses;
 
@@ -561,6 +606,8 @@ in
                   $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"'
                   ${userPermissions}
                   ${userClauses}
+
+                  ${dbOwnershipStmt}
                 ''
               )
               cfg.ensureUsers
@@ -577,7 +624,7 @@ in
                    else "simple";
 
             # Shut down Postgres using SIGINT ("Fast Shutdown mode").  See
-            # http://www.postgresql.org/docs/current/static/server-shutdown.html
+            # https://www.postgresql.org/docs/current/server-shutdown.html
             KillSignal = "SIGINT";
             KillMode = "mixed";
 
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index 1464f4487e3..315a0282cd7 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -63,7 +63,7 @@ in {
 
       vmOverCommit = mkEnableOption (lib.mdDoc ''
         setting of vm.overcommit_memory to 1
-        (Suggested for Background Saving: http://redis.io/topics/faq)
+        (Suggested for Background Saving: <https://redis.io/docs/get-started/faq/>)
       '');
 
       servers = mkOption {
@@ -75,7 +75,7 @@ in {
               Note that the NixOS module for Redis disables kernel support
               for Transparent Huge Pages (THP),
               because this features causes major performance problems for Redis,
-              e.g. (https://redis.io/topics/latency).
+              e.g. (https://redis.io/topics/latency)
             '');
 
             user = mkOption {
diff --git a/nixos/modules/services/databases/surrealdb.nix b/nixos/modules/services/databases/surrealdb.nix
index 28bd97cd731..e1a1faed1f8 100644
--- a/nixos/modules/services/databases/surrealdb.nix
+++ b/nixos/modules/services/databases/surrealdb.nix
@@ -8,7 +8,7 @@ in {
 
   options = {
     services.surrealdb = {
-      enable = mkEnableOption (lib.mdDoc "A scalable, distributed, collaborative, document-graph database, for the realtime web ");
+      enable = mkEnableOption (lib.mdDoc "SurrealDB, a scalable, distributed, collaborative, document-graph database, for the realtime web");
 
       package = mkOption {
         default = pkgs.surrealdb;
diff --git a/nixos/modules/services/desktops/deepin/app-services.nix b/nixos/modules/services/desktops/deepin/app-services.nix
index 6f9932e4873..4592bc7bb34 100644
--- a/nixos/modules/services/desktops/deepin/app-services.nix
+++ b/nixos/modules/services/desktops/deepin/app-services.nix
@@ -14,7 +14,7 @@ with lib;
 
     services.deepin.app-services = {
 
-      enable = mkEnableOption (lib.mdDoc "Service collection of DDE applications, including dconfig-center");
+      enable = mkEnableOption (lib.mdDoc "service collection of DDE applications, including dconfig-center");
 
     };
 
diff --git a/nixos/modules/services/desktops/deepin/dde-api.nix b/nixos/modules/services/desktops/deepin/dde-api.nix
index 472d9860c10..459876febf2 100644
--- a/nixos/modules/services/desktops/deepin/dde-api.nix
+++ b/nixos/modules/services/desktops/deepin/dde-api.nix
@@ -15,8 +15,8 @@ with lib;
     services.deepin.dde-api = {
 
       enable = mkEnableOption (lib.mdDoc ''
-        Provides some dbus interfaces that is used for screen zone detecting,
-        thumbnail generating, and sound playing in Deepin Desktop Environment.
+        some dbus interfaces that is used for screen zone detecting,
+        thumbnail generating, and sound playing in Deepin Desktop Environment
       '');
 
     };
diff --git a/nixos/modules/services/desktops/deepin/dde-daemon.nix b/nixos/modules/services/desktops/deepin/dde-daemon.nix
index 9377f523ebf..356d323bcbd 100644
--- a/nixos/modules/services/desktops/deepin/dde-daemon.nix
+++ b/nixos/modules/services/desktops/deepin/dde-daemon.nix
@@ -14,7 +14,7 @@ with lib;
 
     services.deepin.dde-daemon = {
 
-      enable = mkEnableOption (lib.mdDoc "Daemon for handling the deepin session settings");
+      enable = mkEnableOption (lib.mdDoc "daemon for handling the deepin session settings");
 
     };
 
diff --git a/nixos/modules/services/desktops/gnome/at-spi2-core.nix b/nixos/modules/services/desktops/gnome/at-spi2-core.nix
index 10a2f1f9eca..d0320c1e630 100644
--- a/nixos/modules/services/desktops/gnome/at-spi2-core.nix
+++ b/nixos/modules/services/desktops/gnome/at-spi2-core.nix
@@ -51,7 +51,7 @@ with lib;
     })
 
     (mkIf (!config.services.gnome.at-spi2-core.enable) {
-      environment.variables = {
+      environment.sessionVariables = {
         NO_AT_BRIDGE = "1";
         GTK_A11Y = "none";
       };
diff --git a/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix b/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
index 5d4ddce9422..4f680eabbe1 100644
--- a/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
+++ b/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
@@ -24,8 +24,8 @@ in
 
   options = {
     services.gnome.gnome-browser-connector.enable = mkEnableOption (mdDoc ''
-      Native host connector for the GNOME Shell browser extension, a DBus service
-      allowing to install GNOME Shell extensions from a web browser.
+      native host connector for the GNOME Shell browser extension, a DBus service
+      allowing to install GNOME Shell extensions from a web browser
     '');
   };
 
@@ -42,6 +42,6 @@ in
 
     services.dbus.packages = [ pkgs.gnome-browser-connector ];
 
-    nixpkgs.config.firefox.enableGnomeExtensions = true;
+    programs.firefox.nativeMessagingHosts.packages = [ pkgs.gnome-browser-connector ];
   };
 }
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index ae695baf42c..07ca2727cf4 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -115,8 +115,7 @@ in {
     environment.systemPackages = [ cfg.package ]
                                  ++ lib.optional cfg.jack.enable jack-libs;
 
-    systemd.packages = [ cfg.package ]
-                       ++ lib.optional cfg.pulse.enable cfg.package.pulse;
+    systemd.packages = [ cfg.package ];
 
     # PipeWire depends on DBUS but doesn't list it. Without this booting
     # into a terminal results in the service crashing with an error.
@@ -130,9 +129,13 @@ in {
     systemd.user.sockets.pipewire.enable = !cfg.systemWide;
     systemd.user.services.pipewire.enable = !cfg.systemWide;
 
+    # Mask pw-pulse if it's not wanted
+    systemd.user.services.pipewire-pulse.enable = cfg.pulse.enable;
+    systemd.user.sockets.pipewire-pulse.enable = cfg.pulse.enable;
+
     systemd.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
     systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
-    systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf (cfg.socketActivation && cfg.pulse.enable) ["sockets.target"];
+    systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
 
     services.udev.packages = [ cfg.package ];
 
@@ -140,14 +143,14 @@ in {
     environment.etc."alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable {
       text = ''
         pcm_type.pipewire {
-          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;
+          libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;
           ${optionalString enable32BitAlsaPlugins
-            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"}
+            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"}
         }
         ctl_type.pipewire {
-          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;
+          libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;
           ${optionalString enable32BitAlsaPlugins
-            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"}
+            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"}
         }
       '';
     };
diff --git a/nixos/modules/services/development/livebook.md b/nixos/modules/services/development/livebook.md
new file mode 100644
index 00000000000..73ddc57f617
--- /dev/null
+++ b/nixos/modules/services/development/livebook.md
@@ -0,0 +1,39 @@
+# Livebook {#module-services-livebook}
+
+[Livebook](https://livebook.dev/) is a web application for writing
+interactive and collaborative code notebooks.
+
+## Basic Usage {#module-services-livebook-basic-usage}
+
+Enabling the `livebook` service creates a user
+[`systemd`](https://www.freedesktop.org/wiki/Software/systemd/) unit
+which runs the server.
+
+```
+{ ... }:
+
+{
+  services.livebook = {
+    enableUserService = true;
+    port = 20123;
+    # See note below about security
+    environmentFile = pkgs.writeText "livebook.env" ''
+      LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+    '';
+  };
+}
+```
+
+::: {.note}
+
+The Livebook server has the ability to run any command as the user it
+is running under, so securing access to it with a password is highly
+recommended.
+
+Putting the password in the Nix configuration like above is an easy
+way to get started but it is not recommended in the real world because
+the `livebook.env` file will be added to the world-readable Nix store.
+A better approach would be to put the password in some secure
+user-readable location and set `environmentFile = /home/user/secure/livebook.env`.
+
+:::
diff --git a/nixos/modules/services/development/livebook.nix b/nixos/modules/services/development/livebook.nix
new file mode 100644
index 00000000000..3991a4125ec
--- /dev/null
+++ b/nixos/modules/services/development/livebook.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.livebook;
+in
+{
+  options.services.livebook = {
+    # Since livebook doesn't have a granular permission system (a user
+    # either has access to all the data or none at all), the decision
+    # was made to run this as a user service.  If that changes in the
+    # future, this can be changed to a system service.
+    enableUserService = mkEnableOption "a user service for Livebook";
+
+    environmentFile = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Environment file as defined in {manpage}`systemd.exec(5)` passed to the service.
+
+        This must contain at least `LIVEBOOK_PASSWORD` or
+        `LIVEBOOK_TOKEN_ENABLED=false`.  See `livebook server --help`
+        for other options.'';
+    };
+
+    erlang_node_short_name = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "livebook";
+      description = "A short name for the distributed node.";
+    };
+
+    erlang_node_name = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "livebook@127.0.0.1";
+      description = "The name for the app distributed node.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = "The port to start the web application on.";
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = lib.mdDoc ''
+        The address to start the web application on.  Must be a valid IPv4 or
+        IPv6 address.
+      '';
+    };
+
+    options = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      description = lib.mdDoc ''
+        Additional options to pass as command-line arguments to the server.
+      '';
+      example = literalExpression ''
+        {
+          cookie = "a value shared by all nodes in this cluster";
+        }
+      '';
+    };
+  };
+
+  config = mkIf cfg.enableUserService {
+    systemd.user.services.livebook = {
+      serviceConfig = {
+        Restart = "always";
+        EnvironmentFile = cfg.environmentFile;
+        ExecStart =
+          let
+            args = lib.cli.toGNUCommandLineShell { } ({
+              inherit (cfg) port;
+              ip = cfg.address;
+              name = cfg.erlang_node_name;
+              sname = cfg.erlang_node_short_name;
+            } // cfg.options);
+          in
+          "${pkgs.livebook}/bin/livebook server ${args}";
+      };
+      path = [ pkgs.bash ];
+      wantedBy = [ "default.target" ];
+    };
+  };
+
+  meta.doc = ./livebook.md;
+}
diff --git a/nixos/modules/services/development/zammad.nix b/nixos/modules/services/development/zammad.nix
index 7dd143eebf1..d24ed24ef39 100644
--- a/nixos/modules/services/development/zammad.nix
+++ b/nixos/modules/services/development/zammad.nix
@@ -204,7 +204,7 @@ in
 
     assertions = [
       {
-        assertion = cfg.database.createLocally -> cfg.database.user == "zammad";
+        assertion = cfg.database.createLocally -> cfg.database.user == "zammad" && cfg.database.name == "zammad";
         message = "services.zammad.database.user must be set to \"zammad\" if services.zammad.database.createLocally is set to true";
       }
       {
@@ -231,7 +231,7 @@ in
       ensureUsers = [
         {
           name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/display-managers/greetd.nix b/nixos/modules/services/display-managers/greetd.nix
index 3a0f59f62af..89cb81f3a78 100644
--- a/nixos/modules/services/display-managers/greetd.nix
+++ b/nixos/modules/services/display-managers/greetd.nix
@@ -59,6 +59,7 @@ in
     security.pam.services.greetd = {
       allowNullPassword = true;
       startSession = true;
+      enableGnomeKeyring = mkDefault config.services.gnome.gnome-keyring.enable;
     };
 
     # This prevents nixos-rebuild from killing greetd by activating getty again
diff --git a/nixos/modules/services/finance/odoo.nix b/nixos/modules/services/finance/odoo.nix
index eec7c4e30cc..b8574ed09af 100644
--- a/nixos/modules/services/finance/odoo.nix
+++ b/nixos/modules/services/finance/odoo.nix
@@ -121,7 +121,7 @@ in
       ensureDatabases = [ "odoo" ];
       ensureUsers = [{
         name = "odoo";
-        ensurePermissions = { "DATABASE odoo" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
     };
   });
diff --git a/nixos/modules/services/games/asf.nix b/nixos/modules/services/games/asf.nix
index f15d7077d96..432de6336ce 100644
--- a/nixos/modules/services/games/asf.nix
+++ b/nixos/modules/services/games/asf.nix
@@ -187,29 +187,41 @@ in
             Group = "asf";
             WorkingDirectory = cfg.dataDir;
             Type = "simple";
-            ExecStart = "${cfg.package}/bin/ArchiSteamFarm --path ${cfg.dataDir} --process-required --no-restart --service --no-config-migrate";
+            ExecStart = "${lib.getExe cfg.package} --no-restart --process-required --service --system-required --path ${cfg.dataDir}";
             Restart = "always";
 
-            # mostly copied from the default systemd service
-            PrivateTmp = true;
+            # copied from the default systemd service at
+            # https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/ArchiSteamFarm/overlay/variant-base/linux/ArchiSteamFarm%40.service
+            CapabilityBoundingSet = "";
+            DevicePolicy = "closed";
             LockPersonality = true;
+            NoNewPrivileges = true;
             PrivateDevices = true;
             PrivateIPC = true;
             PrivateMounts = true;
+            PrivateTmp = true; # instead of rw /tmp
             PrivateUsers = true;
+            ProcSubset = "pid";
             ProtectClock = true;
             ProtectControlGroups = true;
+            ProtectHome = true;
             ProtectHostname = true;
             ProtectKernelLogs = true;
             ProtectKernelModules = true;
             ProtectKernelTunables = true;
             ProtectProc = "invisible";
-            ProtectSystem = "full";
+            ProtectSystem = "strict";
             RemoveIPC = true;
-            RestrictAddressFamilies = "AF_INET AF_INET6";
+            RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK AF_UNIX";
             RestrictNamespaces = true;
             RestrictRealtime = true;
             RestrictSUIDSGID = true;
+            SystemCallArchitectures = "native";
+            UMask = "0077";
+
+            # we luckily already have systemd v247+
+            SecureBits = "noroot-locked";
+            SystemCallFilter = [ "@system-service" "~@privileged" ];
           }
         ];
 
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index 4e5913fd275..7b6c336bd22 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -181,7 +181,25 @@ in {
     # required to update the firmware of disks
     services.udisks2.enable = true;
 
-    systemd.packages = [ cfg.package ];
+    systemd = {
+      packages = [ cfg.package ];
+
+      # fwupd-refresh expects a user that we do not create, so just run with DynamicUser
+      # instead and ensure we take ownership of /var/lib/fwupd
+      services.fwupd-refresh.serviceConfig = {
+        StateDirectory = "fwupd";
+        # Better for debugging, upstream sets stderr to null for some reason..
+        StandardError = "inherit";
+      };
+
+      timers.fwupd-refresh.wantedBy = [ "timers.target" ];
+    };
+
+    users.users.fwupd-refresh = {
+      isSystemUser = true;
+      group = "fwupd-refresh";
+    };
+    users.groups.fwupd-refresh = {};
 
     security.polkit.enable = true;
   };
diff --git a/nixos/modules/services/hardware/iptsd.nix b/nixos/modules/services/hardware/iptsd.nix
new file mode 100644
index 00000000000..8af0a6d6bbe
--- /dev/null
+++ b/nixos/modules/services/hardware/iptsd.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.iptsd;
+  format = pkgs.formats.ini { };
+  configFile = format.generate "iptsd.conf" cfg.config;
+in {
+  options.services.iptsd = {
+    enable = lib.mkEnableOption (lib.mdDoc "the userspace daemon for Intel Precise Touch & Stylus");
+
+    config = lib.mkOption {
+      default = { };
+      description = lib.mdDoc ''
+        Configuration for IPTSD. See the
+        [reference configuration](https://github.com/linux-surface/iptsd/blob/master/etc/iptsd.conf)
+        for available options and defaults.
+      '';
+      type = lib.types.submodule {
+        freeformType = format.type;
+        options = {
+          Touch = {
+            DisableOnPalm = lib.mkOption {
+              default = false;
+              description = lib.mdDoc "Ignore all touch inputs if a palm was registered on the display.";
+              type = lib.types.bool;
+            };
+            DisableOnStylus = lib.mkOption {
+              default = false;
+              description = lib.mdDoc "Ignore all touch inputs if a stylus is in proximity.";
+              type = lib.types.bool;
+            };
+          };
+          Stylus = {
+            Disable = lib.mkOption {
+              default = false;
+              description = lib.mdDoc "Disables the stylus. No stylus data will be processed.";
+              type = lib.types.bool;
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [ pkgs.iptsd ];
+    environment.etc."iptsd.conf".source = configFile;
+    systemd.services."iptsd@".restartTriggers = [ configFile ];
+    services.udev.packages = [ pkgs.iptsd ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ dotlambda ];
+}
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
index 7d544050130..aac20c6c760 100644
--- a/nixos/modules/services/hardware/kanata.nix
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -9,8 +9,14 @@ let
     options = {
       devices = mkOption {
         type = types.listOf types.str;
+        default = [ ];
         example = [ "/dev/input/by-id/usb-0000_0000-event-kbd" ];
-        description = mdDoc "Paths to keyboard devices.";
+        description = mdDoc ''
+          Paths to keyboard devices.
+
+          An empty list, the default value, lets kanata detect which
+          input devices are keyboards and intercept them all.
+        '';
       };
       config = mkOption {
         type = types.lines;
@@ -162,6 +168,14 @@ in
   };
 
   config = mkIf cfg.enable {
+    warnings =
+      let
+        keyboardsWithEmptyDevices = filterAttrs (name: keyboard: keyboard.devices == [ ]) cfg.keyboards;
+        existEmptyDevices = length (attrNames keyboardsWithEmptyDevices) > 0;
+        moreThanOneKeyboard = length (attrNames cfg.keyboards) > 1;
+      in
+      optional (existEmptyDevices && moreThanOneKeyboard) "One device can only be intercepted by one kanata instance.  Setting services.kanata.keyboards.${head (attrNames keyboardsWithEmptyDevices)}.devices = [ ] and using more than one services.kanata.keyboards may cause a race condition.";
+
     hardware.uinput.enable = true;
 
     systemd.services = mapAttrs' mkService cfg.keyboards;
diff --git a/nixos/modules/services/hardware/keyd.nix b/nixos/modules/services/hardware/keyd.nix
index ead2f456a20..724e9b95684 100644
--- a/nixos/modules/services/hardware/keyd.nix
+++ b/nixos/modules/services/hardware/keyd.nix
@@ -2,7 +2,6 @@
 with lib;
 let
   cfg = config.services.keyd;
-  settingsFormat = pkgs.formats.ini { };
 
   keyboardOptions = { ... }: {
     options = {
@@ -16,7 +15,7 @@ let
       };
 
       settings = mkOption {
-        type = settingsFormat.type;
+        type = (pkgs.formats.ini { }).type;
         default = { };
         example = {
           main = {
@@ -37,6 +36,20 @@ let
           See <https://github.com/rvaiya/keyd> how to configure.
         '';
       };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          [control+shift]
+          h = left
+        '';
+        description = lib.mdDoc ''
+          Extra configuration that is appended to the end of the file.
+          **Do not** write `ids` section here, use a separate option for it.
+          You can use this option to define compound layers that must always be defined after the layer they are comprised.
+        '';
+      };
     };
   };
 in
@@ -85,15 +98,12 @@ in
     environment.etc = mapAttrs'
       (name: options:
         nameValuePair "keyd/${name}.conf" {
-          source = pkgs.runCommand "${name}.conf"
-            {
-              ids = ''
-                [ids]
-                ${concatStringsSep "\n" options.ids}
-              '';
-              passAsFile = [ "ids" ];
-            } ''
-            cat $idsPath <(echo) ${settingsFormat.generate "keyd-${name}.conf" options.settings} >$out
+          text = ''
+            [ids]
+            ${concatStringsSep "\n" options.ids}
+
+            ${generators.toINI {} options.settings}
+            ${options.extraConfig}
           '';
         })
       cfg.keyboards;
diff --git a/nixos/modules/services/hardware/supergfxd.nix b/nixos/modules/services/hardware/supergfxd.nix
index bd82775e824..f7af993d723 100644
--- a/nixos/modules/services/hardware/supergfxd.nix
+++ b/nixos/modules/services/hardware/supergfxd.nix
@@ -7,7 +7,7 @@ in
 {
   options = {
     services.supergfxd = {
-      enable = lib.mkEnableOption (lib.mdDoc "Enable the supergfxd service");
+      enable = lib.mkEnableOption (lib.mdDoc "the supergfxd service");
 
       settings = lib.mkOption {
         type = lib.types.nullOr json.type;
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
index afca24d976e..0f1f00348ee 100644
--- a/nixos/modules/services/hardware/throttled.nix
+++ b/nixos/modules/services/hardware/throttled.nix
@@ -27,10 +27,10 @@ in {
       then pkgs.writeText "throttled.conf" cfg.extraConfig
       else "${pkgs.throttled}/etc/throttled.conf";
 
+    hardware.cpu.x86.msr.enable = true;
     # Kernel 5.9 spams warnings whenever userspace writes to CPU MSRs.
     # See https://github.com/erpalma/throttled/issues/215
-    boot.kernelParams =
-      optional (versionAtLeast config.boot.kernelPackages.kernel.version "5.9")
-      "msr.allow_writes=on";
+    hardware.cpu.x86.msr.settings.allow-writes =
+      mkIf (versionAtLeast config.boot.kernelPackages.kernel.version "5.9") "on";
   };
 }
diff --git a/nixos/modules/services/hardware/tlp.nix b/nixos/modules/services/hardware/tlp.nix
index d2cc7c661c6..0b7f98ab6a6 100644
--- a/nixos/modules/services/hardware/tlp.nix
+++ b/nixos/modules/services/hardware/tlp.nix
@@ -47,7 +47,7 @@ in
 
   ###### implementation
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "msr" ];
+    hardware.cpu.x86.msr.enable = true;
 
     warnings = optional (cfg.extraConfig != "") ''
       Using config.services.tlp.extraConfig is deprecated and will become unsupported in a future release. Use config.services.tlp.settings instead.
@@ -65,7 +65,7 @@ in
       "tlp.conf".text = (mkTlpConfig cfg.settings) + cfg.extraConfig;
     } // optionalAttrs enableRDW {
       "NetworkManager/dispatcher.d/99tlp-rdw-nm".source =
-        "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm";
+        "${tlp}/usr/lib/NetworkManager/dispatcher.d/99tlp-rdw-nm";
     };
 
     environment.systemPackages = [ tlp ];
diff --git a/nixos/modules/services/hardware/tuxedo-rs.nix b/nixos/modules/services/hardware/tuxedo-rs.nix
new file mode 100644
index 00000000000..0daccfef3a5
--- /dev/null
+++ b/nixos/modules/services/hardware/tuxedo-rs.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.tuxedo-rs;
+
+in
+{
+  options = {
+    hardware.tuxedo-rs = {
+      enable = mkEnableOption (lib.mdDoc "Rust utilities for interacting with hardware from TUXEDO Computers");
+
+      tailor-gui.enable = mkEnableOption (lib.mdDoc "tailor-gui, an alternative to TUXEDO Control Center, written in Rust");
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      hardware.tuxedo-keyboard.enable = true;
+
+      systemd = {
+        services.tailord = {
+          enable = true;
+          description = "Tuxedo Tailor hardware control service";
+          after = [ "systemd-logind.service" ];
+          wantedBy = [ "multi-user.target" ];
+
+          serviceConfig = {
+            Type = "dbus";
+            BusName = "com.tux.Tailor";
+            ExecStart = "${pkgs.tuxedo-rs}/bin/tailord";
+            Environment = "RUST_BACKTRACE=1";
+            Restart = "on-failure";
+          };
+        };
+      };
+
+      services.dbus.packages = [ pkgs.tuxedo-rs ];
+
+      environment.systemPackages = [ pkgs.tuxedo-rs ];
+    }
+    (mkIf cfg.tailor-gui.enable {
+      environment.systemPackages = [ pkgs.tailor-gui ];
+    })
+  ]);
+
+  meta.maintainers = with maintainers; [ mrcjkb ];
+}
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 56120094871..08ca7a0d247 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -279,7 +279,7 @@ in
       default = true;
       type = types.bool;
       description = lib.mdDoc ''
-        Whether to assign [predictable names to network interfaces](http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames).
+        Whether to assign [predictable names to network interfaces](https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/).
         If enabled, interfaces
         are assigned names that contain topology information
         (e.g. `wlp3s0`) and thus should be stable
@@ -350,7 +350,7 @@ in
 
     boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
 
-    boot.initrd.extraUdevRulesCommands = optionalString (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
+    boot.initrd.extraUdevRulesCommands = mkIf (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
       ''
         cat <<'EOF' > $out/99-local.rules
         ${config.boot.initrd.services.udev.rules}
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix
index 94477747540..258f09bbab0 100644
--- a/nixos/modules/services/hardware/undervolt.nix
+++ b/nixos/modules/services/hardware/undervolt.nix
@@ -159,7 +159,7 @@ in
   };
 
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "msr" ];
+    hardware.cpu.x86.msr.enable = true;
 
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/services/home-automation/esphome.nix b/nixos/modules/services/home-automation/esphome.nix
index d7dbb6f0b90..080c8876382 100644
--- a/nixos/modules/services/home-automation/esphome.nix
+++ b/nixos/modules/services/home-automation/esphome.nix
@@ -112,7 +112,7 @@ in
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
         ProtectProc = "invisible";
-        ProcSubset = "pid";
+        ProcSubset = "all"; # Using "pid" breaks bwrap
         ProtectSystem = "strict";
         #RemoveIPC = true; # Implied by DynamicUser
         RestrictAddressFamilies = [
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index 0b8b1d71941..54fd3e17292 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -12,11 +12,12 @@ let
   # We post-process the result to add support for YAML functions, like secrets or includes, see e.g.
   # https://www.home-assistant.io/docs/configuration/secrets/
   filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) cfg.config or {};
-  configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
+  configFile = pkgs.runCommandLocal "configuration.yaml" { } ''
     cp ${format.generate "configuration.yaml" filteredConfig} $out
     sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
   '';
-  lovelaceConfig = cfg.lovelaceConfig or {};
+  lovelaceConfig = if (cfg.lovelaceConfig == null) then {}
+    else (lib.recursiveUpdate customLovelaceModulesResources cfg.lovelaceConfig);
   lovelaceConfigFile = format.generate "ui-lovelace.yaml" lovelaceConfig;
 
   # Components advertised by the home-assistant package
@@ -62,8 +63,24 @@ let
     # Respect overrides that already exist in the passed package and
     # concat it with values passed via the module.
     extraComponents = oldArgs.extraComponents or [] ++ extraComponents;
-    extraPackages = ps: (oldArgs.extraPackages or (_: []) ps) ++ (cfg.extraPackages ps);
+    extraPackages = ps: (oldArgs.extraPackages or (_: []) ps)
+      ++ (cfg.extraPackages ps)
+      ++ (lib.concatMap (component: component.propagatedBuildInputs or []) cfg.customComponents);
   }));
+
+  # Create a directory that holds all lovelace modules
+  customLovelaceModulesDir = pkgs.buildEnv {
+    name = "home-assistant-custom-lovelace-modules";
+    paths = cfg.customLovelaceModules;
+  };
+
+  # Create parts of the lovelace config that reference lovelave modules as resources
+  customLovelaceModulesResources = {
+    lovelace.resources = map (card: {
+      url = "/local/nixos-lovelace-modules/${card.entrypoint or card.pname}.js?${card.version}";
+      type = "module";
+    }) cfg.customLovelaceModules;
+  };
 in {
   imports = [
     # Migrations in NixOS 22.05
@@ -137,6 +154,41 @@ in {
       '';
     };
 
+    customComponents = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression ''
+        with pkgs.home-assistant-custom-components; [
+          prometheus-sensor
+        ];
+      '';
+      description = lib.mdDoc ''
+        List of custom component packages to install.
+
+        Available components can be found below `pkgs.home-assistant-custom-components`.
+      '';
+    };
+
+    customLovelaceModules = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression ''
+        with pkgs.home-assistant-custom-lovelace-modules; [
+          mini-graph-card
+          mini-media-player
+        ];
+      '';
+      description = lib.mdDoc ''
+        List of custom lovelace card packages to load as lovelace resources.
+
+        Available cards can be found below `pkgs.home-assistant-custom-lovelace-modules`.
+
+        ::: {.note}
+        Automatic loading only works with lovelace in `yaml` mode.
+        :::
+      '';
+    };
+
     config = mkOption {
       type = types.nullOr (types.submodule {
         freeformType = format.type;
@@ -408,9 +460,35 @@ in {
           rm -f "${cfg.configDir}/ui-lovelace.yaml"
           ln -s /etc/home-assistant/ui-lovelace.yaml "${cfg.configDir}/ui-lovelace.yaml"
         '';
+        copyCustomLovelaceModules = if cfg.customLovelaceModules != [] then ''
+          mkdir -p "${cfg.configDir}/www"
+          ln -fns ${customLovelaceModulesDir} "${cfg.configDir}/www/nixos-lovelace-modules"
+        '' else ''
+          rm -f "${cfg.configDir}/www/nixos-lovelace-modules"
+        '';
+        copyCustomComponents = ''
+          mkdir -p "${cfg.configDir}/custom_components"
+
+          # remove components symlinked in from below the /nix/store
+          components="$(find "${cfg.configDir}/custom_components" -maxdepth 1 -type l)"
+          for component in "$components"; do
+            if [[ "$(readlink "$component")" =~ ^${escapeShellArg builtins.storeDir} ]]; then
+              rm "$component"
+            fi
+          done
+
+          # recreate symlinks for desired components
+          declare -a components=(${escapeShellArgs cfg.customComponents})
+          for component in "''${components[@]}"; do
+            path="$(dirname $(find "$component" -name "manifest.json"))"
+            ln -fns "$path" "${cfg.configDir}/custom_components/"
+          done
+        '';
       in
         (optionalString (cfg.config != null) copyConfig) +
-        (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig)
+        (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig) +
+        copyCustomLovelaceModules +
+        copyCustomComponents
       ;
       environment.PYTHONPATH = package.pythonPath;
       serviceConfig = let
@@ -455,12 +533,15 @@ in {
           "govee_ble"
           "homekit_controller"
           "inkbird"
+          "improv_ble"
           "keymitt_ble"
           "led_ble"
+          "medcom_ble"
           "melnor"
           "moat"
           "mopeka"
           "oralb"
+          "private_ble_device"
           "qingping"
           "rapt_ble"
           "ruuvi_gateway"
@@ -586,11 +667,12 @@ in {
           "~@privileged"
         ] ++ optionals (any useComponent componentsUsingPing) [
           "capset"
+          "setuid"
         ];
         UMask = "0077";
       };
       path = [
-        "/run/wrappers" # needed for ping
+        pkgs.unixtools.ping # needed for ping
       ];
     };
 
diff --git a/nixos/modules/services/home-automation/homeassistant-satellite.nix b/nixos/modules/services/home-automation/homeassistant-satellite.nix
new file mode 100644
index 00000000000..e3f0617cf01
--- /dev/null
+++ b/nixos/modules/services/home-automation/homeassistant-satellite.nix
@@ -0,0 +1,225 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.homeassistant-satellite;
+
+  inherit (lib)
+    escapeShellArg
+    escapeShellArgs
+    mkOption
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkPackageOptionMD
+    types
+    ;
+
+  inherit (builtins)
+    toString
+    ;
+
+  # override the package with the relevant vad dependencies
+  package = cfg.package.overridePythonAttrs (oldAttrs: {
+    propagatedBuildInputs = oldAttrs.propagatedBuildInputs
+      ++ lib.optional (cfg.vad == "webrtcvad") cfg.package.optional-dependencies.webrtc
+      ++ lib.optional (cfg.vad == "silero") cfg.package.optional-dependencies.silerovad
+      ++ lib.optional (cfg.pulseaudio.enable) cfg.package.optional-dependencies.pulseaudio;
+  });
+
+in
+
+{
+  meta.buildDocsInSandbox = false;
+
+  options.services.homeassistant-satellite = with types; {
+    enable = mkEnableOption (mdDoc "Home Assistant Satellite");
+
+    package = mkPackageOptionMD pkgs "homeassistant-satellite" { };
+
+    user = mkOption {
+      type = str;
+      example = "alice";
+      description = mdDoc ''
+        User to run homeassistant-satellite under.
+      '';
+    };
+
+    group = mkOption {
+      type = str;
+      default = "users";
+      description = mdDoc ''
+        Group to run homeassistant-satellite under.
+      '';
+    };
+
+    host = mkOption {
+      type = str;
+      example = "home-assistant.local";
+      description = mdDoc ''
+        Hostname on which your Home Assistant instance can be reached.
+      '';
+    };
+
+    port = mkOption {
+      type = port;
+      example = 8123;
+      description = mdDoc ''
+        Port on which your Home Assistance can be reached.
+      '';
+      apply = toString;
+    };
+
+    protocol = mkOption {
+      type = enum [ "http" "https" ];
+      default = "http";
+      example = "https";
+      description = mdDoc ''
+        The transport protocol used to connect to Home Assistant.
+      '';
+    };
+
+    tokenFile = mkOption {
+      type = path;
+      example = "/run/keys/hass-token";
+      description = mdDoc ''
+        Path to a file containing a long-lived access token for your Home Assistant instance.
+      '';
+      apply = escapeShellArg;
+    };
+
+    sounds = {
+      awake = mkOption {
+        type = nullOr str;
+        default = null;
+        description = mdDoc ''
+          Audio file to play when the wake word is detected.
+        '';
+      };
+
+      done = mkOption {
+        type = nullOr str;
+        default = null;
+        description = mdDoc ''
+          Audio file to play when the voice command is done.
+        '';
+      };
+    };
+
+    vad = mkOption {
+      type = enum [ "disabled" "webrtcvad" "silero" ];
+      default = "disabled";
+      example = "silero";
+      description = mdDoc ''
+        Voice activity detection model. With `disabled` sound will be transmitted continously.
+      '';
+    };
+
+    pulseaudio = {
+      enable = mkEnableOption "recording/playback via PulseAudio or PipeWire";
+
+      socket = mkOption {
+        type = nullOr str;
+        default = null;
+        example = "/run/user/1000/pulse/native";
+        description = mdDoc ''
+          Path or hostname to connect with the PulseAudio server.
+        '';
+      };
+
+      duckingVolume = mkOption {
+        type = nullOr float;
+        default = null;
+        example = 0.4;
+        description = mdDoc ''
+          Reduce output volume (between 0 and 1) to this percentage value while recording.
+        '';
+      };
+
+      echoCancellation = mkEnableOption "acoustic echo cancellation";
+    };
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [ ];
+      description = mdDoc ''
+        Extra arguments to pass to the commandline.
+      '';
+      apply = escapeShellArgs;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services."homeassistant-satellite" = {
+      description = "Home Assistant Satellite";
+      after = [
+        "network-online.target"
+      ];
+      wants = [
+        "network-online.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      path = with pkgs; [
+        ffmpeg-headless
+      ] ++ lib.optionals (!cfg.pulseaudio.enable) [
+        alsa-utils
+      ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        # https://github.com/rhasspy/hassio-addons/blob/master/assist_microphone/rootfs/etc/s6-overlay/s6-rc.d/assist_microphone/run
+        ExecStart = ''
+          ${package}/bin/homeassistant-satellite \
+            --host ${cfg.host} \
+            --port ${cfg.port} \
+            --protocol ${cfg.protocol} \
+            --token-file ${cfg.tokenFile} \
+            --vad ${cfg.vad} \
+            ${lib.optionalString cfg.pulseaudio.enable "--pulseaudio"}${lib.optionalString (cfg.pulseaudio.socket != null) "=${cfg.pulseaudio.socket}"} \
+            ${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.duckingVolume != null) "--ducking-volume=${toString cfg.pulseaudio.duckingVolume}"} \
+            ${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.echoCancellation) "--echo-cancel"} \
+            ${lib.optionalString (cfg.sounds.awake != null) "--awake-sound=${toString cfg.sounds.awake}"} \
+            ${lib.optionalString (cfg.sounds.done != null) "--done-sound=${toString cfg.sounds.done}"} \
+            ${cfg.extraArgs}
+        '';
+        CapabilityBoundingSet = "";
+        DeviceAllow = "";
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Operation not permitted
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHome = false; # Would deny access to local pulse/pipewire server
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectProc = "invisible";
+        ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo
+        Restart = "always";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SupplementaryGroups = [
+          "audio"
+        ];
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/home-automation/zwave-js.nix b/nixos/modules/services/home-automation/zwave-js.nix
new file mode 100644
index 00000000000..87c9b8f1ac8
--- /dev/null
+++ b/nixos/modules/services/home-automation/zwave-js.nix
@@ -0,0 +1,152 @@
+{config, pkgs, lib, ...}:
+
+with lib;
+
+let
+  cfg = config.services.zwave-js;
+  mergedConfigFile = "/run/zwave-js/config.json";
+  settingsFormat = pkgs.formats.json {};
+in {
+  options.services.zwave-js = {
+    enable = mkEnableOption (mdDoc "the zwave-js server on boot");
+
+    package = mkPackageOptionMD pkgs "zwave-js-server" { };
+
+    port = mkOption {
+      type = types.port;
+      default = 3000;
+      description = mdDoc ''
+        Port for the server to listen on.
+      '';
+    };
+
+    serialPort = mkOption {
+      type = types.path;
+      description = mdDoc ''
+        Serial port device path for Z-Wave controller.
+      '';
+      example = "/dev/ttyUSB0";
+    };
+
+    secretsConfigFile = mkOption {
+      type = types.path;
+      description = mdDoc ''
+        JSON file containing secret keys. A dummy example:
+
+        ```
+        {
+          "securityKeys": {
+            "S0_Legacy": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+            "S2_Unauthenticated": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
+            "S2_Authenticated": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
+            "S2_AccessControl": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
+          }
+        }
+        ```
+
+        See
+        <https://zwave-js.github.io/node-zwave-js/#/getting-started/security-s2>
+        for details. This file will be merged with the module-generated config
+        file (taking precedence).
+
+        Z-Wave keys can be generated with:
+
+          {command}`< /dev/urandom tr -dc A-F0-9 | head -c32 ;echo`
+
+
+        ::: {.warning}
+        A file in the nix store should not be used since it will be readable to
+        all users.
+        :::
+      '';
+      example = "/secrets/zwave-js-keys.json";
+    };
+
+    settings = mkOption {
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+
+        options = {
+          storage = {
+            cacheDir = mkOption {
+              type = types.path;
+              default = "/var/cache/zwave-js";
+              readOnly = true;
+              description = lib.mdDoc "Cache directory";
+            };
+          };
+        };
+      };
+      default = {};
+      description = mdDoc ''
+        Configuration settings for the generated config
+        file.
+      '';
+    };
+
+    extraFlags = lib.mkOption {
+      type = with lib.types; listOf str;
+      default = [ ];
+      example = [ "--mock-driver" ];
+      description = lib.mdDoc ''
+        Extra flags to pass to command
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.zwave-js = let
+      configFile = settingsFormat.generate "zwave-js-config.json" cfg.settings;
+    in {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = "Z-Wave JS Server";
+      serviceConfig = {
+        ExecStartPre = ''
+          /bin/sh -c "${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${configFile} ${cfg.secretsConfigFile} > ${mergedConfigFile}"
+        '';
+        ExecStart = lib.concatStringsSep " " [
+          "${cfg.package}/bin/zwave-server"
+          "--config ${mergedConfigFile}"
+          "--port ${toString cfg.port}"
+          cfg.serialPort
+          (escapeShellArgs cfg.extraFlags)
+        ];
+        Restart = "on-failure";
+        User = "zwave-js";
+        SupplementaryGroups = [ "dialout" ];
+        CacheDirectory = "zwave-js";
+        RuntimeDirectory = "zwave-js";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        DeviceAllow = [cfg.serialPort];
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = false;
+        NoNewPrivileges = true;
+        PrivateUsers = true;
+        PrivateTmp = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        RemoveIPC = true;
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service @pkey"
+          "~@privileged @resources"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ graham33 ];
+}
diff --git a/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix
index e9e3ae1f14c..42d52a61639 100644
--- a/nixos/modules/services/logging/logstash.nix
+++ b/nixos/modules/services/logging/logstash.nix
@@ -123,7 +123,7 @@ in
         example = ''
           if [type] == "syslog" {
             # Keep only relevant systemd fields
-            # http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
+            # https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
             prune {
               whitelist_names => [
                 "type", "@timestamp", "@version",
diff --git a/nixos/modules/services/logging/syslog-ng.nix b/nixos/modules/services/logging/syslog-ng.nix
index d22acbeaa70..48d556b9459 100644
--- a/nixos/modules/services/logging/syslog-ng.nix
+++ b/nixos/modules/services/logging/syslog-ng.nix
@@ -67,7 +67,7 @@ in {
       configHeader = mkOption {
         type = types.lines;
         default = ''
-          @version: 3.6
+          @version: 4.4
           @include "scl.conf"
         '';
         description = lib.mdDoc ''
diff --git a/nixos/modules/services/logging/ulogd.nix b/nixos/modules/services/logging/ulogd.nix
index 065032b531c..05c9797bb28 100644
--- a/nixos/modules/services/logging/ulogd.nix
+++ b/nixos/modules/services/logging/ulogd.nix
@@ -3,7 +3,7 @@
 with lib;
 let
   cfg = config.services.ulogd;
-  settingsFormat = pkgs.formats.ini { };
+  settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; };
   settingsFile = settingsFormat.generate "ulogd.conf" cfg.settings;
 in {
   options = {
@@ -12,22 +12,34 @@ in {
 
       settings = mkOption {
         example = {
-          global.stack = "stack=log1:NFLOG,base1:BASE,pcap1:PCAP";
+          global.stack = [
+            "log1:NFLOG,base1:BASE,ifi1:IFINDEX,ip2str1:IP2STR,print1:PRINTPKT,emu1:LOGEMU"
+            "log1:NFLOG,base1:BASE,pcap1:PCAP"
+          ];
+
           log1.group = 2;
+
           pcap1 = {
+            sync = 1;
             file = "/var/log/ulogd.pcap";
+          };
+
+          emu1 = {
             sync = 1;
+            file = "/var/log/ulogd_pkts.log";
           };
         };
         type = settingsFormat.type;
         default = { };
-        description = lib.mdDoc "Configuration for ulogd. See {file}`/share/doc/ulogd/` in `pkgs.ulogd.doc`.";
+        description = lib.mdDoc
+          "Configuration for ulogd. See {file}`/share/doc/ulogd/` in `pkgs.ulogd.doc`.";
       };
 
       logLevel = mkOption {
         type = types.enum [ 1 3 5 7 8 ];
         default = 5;
-        description = lib.mdDoc "Log level (1 = debug, 3 = info, 5 = notice, 7 = error, 8 = fatal)";
+        description = lib.mdDoc
+          "Log level (1 = debug, 3 = info, 5 = notice, 7 = error, 8 = fatal)";
       };
     };
   };
@@ -40,7 +52,10 @@ in {
       before = [ "network-pre.target" ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.ulogd}/bin/ulogd -c ${settingsFile} --verbose --loglevel ${toString cfg.logLevel}";
+        ExecStart =
+          "${pkgs.ulogd}/bin/ulogd -c ${settingsFile} --verbose --loglevel ${
+            toString cfg.logLevel
+          }";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
       };
     };
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index 21bafd859c3..abbb2f32e6c 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -302,7 +302,7 @@ in
 
     enablePAM = mkEnableOption (lib.mdDoc "creating a own Dovecot PAM service and configure PAM user logins") // { default = true; };
 
-    enableDHE = mkEnableOption (lib.mdDoc "enable ssl_dh and generation of primes for the key exchange") // { default = true; };
+    enableDHE = mkEnableOption (lib.mdDoc "ssl_dh and generation of primes for the key exchange") // { default = true; };
 
     sieveScripts = mkOption {
       type = types.attrsOf types.path;
diff --git a/nixos/modules/services/mail/listmonk.nix b/nixos/modules/services/mail/listmonk.nix
index 11b2a518622..cea1bc95608 100644
--- a/nixos/modules/services/mail/listmonk.nix
+++ b/nixos/modules/services/mail/listmonk.nix
@@ -168,7 +168,7 @@ in {
 
       ensureUsers = [{
         name = "listmonk";
-        ensurePermissions = { "DATABASE listmonk" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
 
       ensureDatabases = [ "listmonk" ];
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index ec2a19f58bb..76035625fbe 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -260,7 +260,7 @@ in {
       };
 
       serve = {
-        enable = mkEnableOption (lib.mdDoc "Automatic nginx and uwsgi setup for mailman-web");
+        enable = mkEnableOption (lib.mdDoc "automatic nginx and uwsgi setup for mailman-web");
 
         virtualRoot = mkOption {
           default = "/";
@@ -314,7 +314,7 @@ in {
         queue_dir = "$var_dir/queue";
         template_dir = "$var_dir/templates";
         log_dir = "/var/log/mailman";
-        lock_dir = "$var_dir/lock";
+        lock_dir = "/run/mailman/lock";
         etc_dir = "/etc";
         pid_file = "/run/mailman/master.pid";
       };
@@ -493,6 +493,9 @@ in {
           RuntimeDirectory = "mailman";
           LogsDirectory = "mailman";
           PIDFile = "/run/mailman/master.pid";
+          Restart = "on-failure";
+          TimeoutStartSec = 180;
+          TimeoutStopSec = 180;
         };
       };
 
@@ -592,10 +595,11 @@ in {
           # Since the mailman-web settings.py obstinately creates a logs
           # dir in the cwd, change to the (writable) runtime directory before
           # starting uwsgi.
-          ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${pkgs.uwsgi.override { plugins = ["python3"]; }}/bin/uwsgi --json ${uwsgiConfigFile}";
+          ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${pkgs.uwsgi.override { plugins = ["python3"]; python3 = webEnv.python; }}/bin/uwsgi --json ${uwsgiConfigFile}";
           User = cfg.webUser;
           Group = "mailman";
           RuntimeDirectory = "mailman-uwsgi";
+          Restart = "on-failure";
         };
       });
 
@@ -620,6 +624,7 @@ in {
           User = cfg.webUser;
           Group = "mailman";
           WorkingDirectory = "/var/lib/mailman-web";
+          Restart = "on-failure";
         };
       };
     } // flip lib.mapAttrs' {
@@ -644,7 +649,7 @@ in {
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ lheckemann qyliss ma27 ];
+    maintainers = with lib.maintainers; [ lheckemann qyliss ];
     doc = ./mailman.md;
   };
 
diff --git a/nixos/modules/services/mail/mlmmj.nix b/nixos/modules/services/mail/mlmmj.nix
index 642f8b20fe3..3f07fabcf17 100644
--- a/nixos/modules/services/mail/mlmmj.nix
+++ b/nixos/modules/services/mail/mlmmj.nix
@@ -143,13 +143,11 @@ in
 
     environment.systemPackages = [ pkgs.mlmmj ];
 
-    system.activationScripts.mlmmj = ''
-          ${pkgs.coreutils}/bin/mkdir -p ${stateDir} ${spoolDir}/${cfg.listDomain}
-          ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${spoolDir}
-          ${concatMapLines (createList cfg.listDomain) cfg.mailLists}
-          ${pkgs.postfix}/bin/postmap /etc/postfix/virtual
-          ${pkgs.postfix}/bin/postmap /etc/postfix/transport
-      '';
+    systemd.tmpfiles.rules = [
+      ''d "${stateDir}" -''
+      ''d "${spoolDir}/${cfg.listDomain}" -''
+      ''Z "${spoolDir}" - "${cfg.user}" "${cfg.group}" -''
+    ];
 
     systemd.services.mlmmj-maintd = {
       description = "mlmmj maintenance daemon";
@@ -158,6 +156,11 @@ in
         Group = cfg.group;
         ExecStart = "${pkgs.mlmmj}/bin/mlmmj-maintd -F -d ${spoolDir}/${cfg.listDomain}";
       };
+      preStart = ''
+        ${concatMapLines (createList cfg.listDomain) cfg.mailLists}
+        ${pkgs.postfix}/bin/postmap /etc/postfix/virtual
+        ${pkgs.postfix}/bin/postmap /etc/postfix/transport
+      '';
     };
 
     systemd.timers.mlmmj-maintd = {
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 22a4e3c451a..4e29f567ed9 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -179,14 +179,22 @@ in
       };
     };
 
+    assertions = [
+      {
+        assertion = localDB -> cfg.database.username == cfg.database.dbname;
+        message = ''
+          When setting up a DB and its owner user, the owner and the DB name must be
+          equal!
+        '';
+      }
+    ];
+
     services.postgresql = mkIf localDB {
       enable = true;
       ensureDatabases = [ cfg.database.dbname ];
       ensureUsers = [ {
         name = cfg.database.username;
-        ensurePermissions = {
-          "DATABASE ${cfg.database.username}" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       } ];
     };
 
diff --git a/nixos/modules/services/mail/sympa.nix b/nixos/modules/services/mail/sympa.nix
index 7a5047b2bea..04ae46f66ee 100644
--- a/nixos/modules/services/mail/sympa.nix
+++ b/nixos/modules/services/mail/sympa.nix
@@ -218,7 +218,7 @@ in
         default = null;
         example = "/run/keys/sympa-dbpassword";
         description = lib.mdDoc ''
-          A file containing the password for {option}`services.sympa.database.user`.
+          A file containing the password for {option}`services.sympa.database.name`.
         '';
       };
 
@@ -342,6 +342,7 @@ in
 
       db_type = cfg.database.type;
       db_name = cfg.database.name;
+      db_user = cfg.database.name;
     }
     // (optionalAttrs (cfg.database.host != null) {
       db_host = cfg.database.host;
@@ -355,9 +356,6 @@ in
     // (optionalAttrs (cfg.database.port != null) {
       db_port = cfg.database.port;
     })
-    // (optionalAttrs (cfg.database.user != null) {
-      db_user = cfg.database.user;
-    })
     // (optionalAttrs (cfg.mta.type == "postfix") {
       sendmail_aliases = "${dataDir}/sympa_transport";
       aliases_program  = "${pkgs.postfix}/bin/postmap";
@@ -393,7 +391,7 @@ in
     users.groups.${group} = {};
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.sympa.database.user must be set to ${user} if services.sympa.database.createLocally is set to true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -579,7 +577,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/matrix/appservice-discord.nix b/nixos/modules/services/matrix/appservice-discord.nix
index f579c2529c0..6ce8718c35d 100644
--- a/nixos/modules/services/matrix/appservice-discord.nix
+++ b/nixos/modules/services/matrix/appservice-discord.nix
@@ -100,9 +100,9 @@ in {
 
       serviceDependencies = mkOption {
         type = with types; listOf str;
-        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        default = optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
         defaultText = literalExpression ''
-          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+          optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
         '';
         description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service,
diff --git a/nixos/modules/services/matrix/matrix-sliding-sync.nix b/nixos/modules/services/matrix/matrix-sliding-sync.nix
index 9bf4de3317c..84bb38f35ae 100644
--- a/nixos/modules/services/matrix/matrix-sliding-sync.nix
+++ b/nixos/modules/services/matrix/matrix-sliding-sync.nix
@@ -7,7 +7,7 @@ in
   options.services.matrix-synapse.sliding-sync = {
     enable = lib.mkEnableOption (lib.mdDoc "sliding sync");
 
-    package = lib.mkPackageOption pkgs "matrix-sliding-sync" { };
+    package = lib.mkPackageOptionMD pkgs "matrix-sliding-sync" { };
 
     settings = lib.mkOption {
       type = lib.types.submodule {
@@ -44,7 +44,7 @@ in
         };
       };
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Freeform environment variables passed to the sliding sync proxy.
         Refer to <https://github.com/matrix-org/sliding-sync#setup> for all supported values.
       '';
@@ -74,14 +74,17 @@ in
     services.postgresql = lib.optionalAttrs cfg.createDatabase {
       enable = true;
       ensureDatabases = [ "matrix-sliding-sync" ];
-      ensureUsers = [ rec {
+      ensureUsers = [ {
         name = "matrix-sliding-sync";
-        ensurePermissions."DATABASE \"${name}\"" = "ALL PRIVILEGES";
+        ensureDBOwnership = true;
       } ];
     };
 
-    systemd.services.matrix-sliding-sync = {
-      after = lib.optional cfg.createDatabase "postgresql.service";
+    systemd.services.matrix-sliding-sync = rec {
+      after =
+        lib.optional cfg.createDatabase "postgresql.service"
+        ++ lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+      wants = after;
       wantedBy = [ "multi-user.target" ];
       environment = cfg.settings;
       serviceConfig = {
@@ -90,6 +93,8 @@ in
         ExecStart = lib.getExe cfg.package;
         StateDirectory = "matrix-sliding-sync";
         WorkingDirectory = "%S/matrix-sliding-sync";
+        Restart = "on-failure";
+        RestartSec = "1s";
       };
     };
   };
diff --git a/nixos/modules/services/matrix/mautrix-facebook.nix b/nixos/modules/services/matrix/mautrix-facebook.nix
index bab6865496d..d7cf024bb80 100644
--- a/nixos/modules/services/matrix/mautrix-facebook.nix
+++ b/nixos/modules/services/matrix/mautrix-facebook.nix
@@ -135,9 +135,7 @@ in {
       ensureDatabases = ["mautrix-facebook"];
       ensureUsers = [{
         name = "mautrix-facebook";
-        ensurePermissions = {
-          "DATABASE \"mautrix-facebook\"" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       }];
     };
 
@@ -145,7 +143,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       wants = [
         "network-online.target"
-      ] ++ optional config.services.matrix-synapse.enable "matrix-synapse.service"
+      ] ++ optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
         ++ optional cfg.configurePostgresql "postgresql.service";
       after = wants;
 
diff --git a/nixos/modules/services/matrix/mautrix-telegram.nix b/nixos/modules/services/matrix/mautrix-telegram.nix
index 97a6ba858e0..168c8bf436a 100644
--- a/nixos/modules/services/matrix/mautrix-telegram.nix
+++ b/nixos/modules/services/matrix/mautrix-telegram.nix
@@ -122,9 +122,9 @@ in {
 
       serviceDependencies = mkOption {
         type = with types; listOf str;
-        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        default = optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
         defaultText = literalExpression ''
-          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+          optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
         '';
         description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service.
diff --git a/nixos/modules/services/matrix/mautrix-whatsapp.nix b/nixos/modules/services/matrix/mautrix-whatsapp.nix
index c4dc4821349..4b561a4b07a 100644
--- a/nixos/modules/services/matrix/mautrix-whatsapp.nix
+++ b/nixos/modules/services/matrix/mautrix-whatsapp.nix
@@ -100,9 +100,9 @@ in {
 
     serviceDependencies = lib.mkOption {
       type = with lib.types; listOf str;
-      default = lib.optional config.services.matrix-synapse.enable "matrix-synapse.service";
+      default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
       defaultText = lib.literalExpression ''
-        optional config.services.matrix-synapse.enable "matrix-synapse.service"
+        optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
       '';
       description = lib.mdDoc ''
         List of Systemd services to require and wait for when starting the application service.
diff --git a/nixos/modules/services/matrix/mjolnir.nix b/nixos/modules/services/matrix/mjolnir.nix
index 0824be66334..4e9a915c23c 100644
--- a/nixos/modules/services/matrix/mjolnir.nix
+++ b/nixos/modules/services/matrix/mjolnir.nix
@@ -96,8 +96,8 @@ in
       type = types.submodule {
         options = {
           enable = mkEnableOption (lib.mdDoc ''
-            If true, accessToken is ignored and the username/password below will be
-            used instead. The access token of the bot will be stored in the dataPath.
+            ignoring the accessToken. If true, accessToken is ignored and the username/password below will be
+            used instead. The access token of the bot will be stored in the dataPath
           '');
 
           username = mkOption {
diff --git a/nixos/modules/services/matrix/mx-puppet-discord.nix b/nixos/modules/services/matrix/mx-puppet-discord.nix
index 36c9f8b122e..70828804b55 100644
--- a/nixos/modules/services/matrix/mx-puppet-discord.nix
+++ b/nixos/modules/services/matrix/mx-puppet-discord.nix
@@ -66,9 +66,9 @@ in {
       };
       serviceDependencies = mkOption {
         type = with types; listOf str;
-        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        default = optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
         defaultText = literalExpression ''
-          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+          optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
         '';
         description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service.
diff --git a/nixos/modules/services/matrix/synapse.md b/nixos/modules/services/matrix/synapse.md
index 1d22805b472..58be24204fc 100644
--- a/nixos/modules/services/matrix/synapse.md
+++ b/nixos/modules/services/matrix/synapse.md
@@ -31,7 +31,7 @@ let
   clientConfig."m.homeserver".base_url = baseUrl;
   serverConfig."m.server" = "${fqdn}:443";
   mkWellKnown = data: ''
-    add_header Content-Type application/json;
+    default_type application/json;
     add_header Access-Control-Allow-Origin *;
     return 200 '${builtins.toJSON data}';
   '';
diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix
index 1354a8cb58b..9cc769c2d0d 100644
--- a/nixos/modules/services/matrix/synapse.nix
+++ b/nixos/modules/services/matrix/synapse.nix
@@ -12,7 +12,9 @@ let
 
   usePostgresql = cfg.settings.database.name == "psycopg2";
   hasLocalPostgresDB = let args = cfg.settings.database.args; in
-    usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]));
+    usePostgresql
+    && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]))
+    && config.services.postgresql.enable;
   hasWorkers = cfg.workers != { };
 
   listenerSupportsResource = resource: listener:
@@ -58,7 +60,6 @@ let
     ++ lib.optional (cfg.settings ? oidc_providers) "oidc"
     ++ lib.optional (cfg.settings ? jwt_config) "jwt"
     ++ lib.optional (cfg.settings ? saml2_config) "saml2"
-    ++ lib.optional (cfg.settings ? opentracing) "opentracing"
     ++ lib.optional (cfg.settings ? redis) "redis"
     ++ lib.optional (cfg.settings ? sentry) "sentry"
     ++ lib.optional (cfg.settings ? user_directory) "user-search"
@@ -70,13 +71,12 @@ let
     inherit (cfg) plugins;
   };
 
-  logConfig = logName: {
+  defaultCommonLogConfig = {
     version = 1;
     formatters.journal_fmt.format = "%(name)s: [%(request)s] %(message)s";
     handlers.journal = {
       class = "systemd.journal.JournalHandler";
       formatter = "journal_fmt";
-      SYSLOG_IDENTIFIER = logName;
     };
     root = {
       level = "INFO";
@@ -84,33 +84,27 @@ let
     };
     disable_existing_loggers = false;
   };
+
+  defaultCommonLogConfigText = generators.toPretty { } defaultCommonLogConfig;
+
   logConfigText = logName:
-    let
-      expr = ''
-        {
-          version = 1;
-          formatters.journal_fmt.format = "%(name)s: [%(request)s] %(message)s";
-          handlers.journal = {
-            class = "systemd.journal.JournalHandler";
-            formatter = "journal_fmt";
-            SYSLOG_IDENTIFIER = "${logName}";
-          };
-          root = {
-            level = "INFO";
-            handlers = [ "journal" ];
-          };
-          disable_existing_loggers = false;
-        };
-      '';
-    in
     lib.literalMD ''
       Path to a yaml file generated from this Nix expression:
 
       ```
-      ${expr}
+      ${generators.toPretty { } (
+        recursiveUpdate defaultCommonLogConfig { handlers.journal.SYSLOG_IDENTIFIER = logName; }
+      )}
       ```
     '';
-  genLogConfigFile = logName: format.generate "synapse-log-${logName}.yaml" (logConfig logName);
+
+  genLogConfigFile = logName: format.generate
+    "synapse-log-${logName}.yaml"
+    (cfg.log // optionalAttrs (cfg.log?handlers.journal) {
+      handlers.journal = cfg.log.handlers.journal // {
+        SYSLOG_IDENTIFIER = logName;
+      };
+    });
 in {
 
   imports = [
@@ -302,6 +296,18 @@ in {
     services.matrix-synapse = {
       enable = mkEnableOption (lib.mdDoc "matrix.org synapse");
 
+      serviceUnit = lib.mkOption {
+        type = lib.types.str;
+        readOnly = true;
+        description = lib.mdDoc ''
+          The systemd unit (a service or a target) for other services to depend on if they
+          need to be started after matrix-synapse.
+
+          This option is useful as the actual parent unit for all matrix-synapse processes
+          changes when configuring workers.
+        '';
+      };
+
       configFile = mkOption {
         type = types.path;
         readOnly = true;
@@ -339,7 +345,6 @@ in {
           [
             "cache-memory" # Provide statistics about caching memory consumption
             "jwt"          # JSON Web Token authentication
-            "opentracing"  # End-to-end tracing support using Jaeger
             "oidc"         # OpenID Connect authentication
             "postgres"     # PostgreSQL database backend
             "redis"        # Redis support for the replication stream between worker processes
@@ -394,6 +399,49 @@ in {
         '';
       };
 
+      log = mkOption {
+        type = types.attrsOf format.type;
+        defaultText = literalExpression defaultCommonLogConfigText;
+        description = mdDoc ''
+          Default configuration for the loggers used by `matrix-synapse` and its workers.
+          The defaults are added with the default priority which means that
+          these will be merged with additional declarations. These additional
+          declarations also take precedence over the defaults when declared
+          with at least normal priority. For instance
+          the log-level for synapse and its workers can be changed like this:
+
+          ```nix
+          { lib, ... }: {
+            services.matrix-synapse.log.root.level = "WARNING";
+          }
+          ```
+
+          And another field can be added like this:
+
+          ```nix
+          {
+            services.matrix-synapse.log = {
+              loggers."synapse.http.matrixfederationclient".level = "DEBUG";
+            };
+          }
+          ```
+
+          Additionally, the field `handlers.journal.SYSLOG_IDENTIFIER` will be added to
+          each log config, i.e.
+          * `synapse` for `matrix-synapse.service`
+          * `synapse-<worker name>` for `matrix-synapse-worker-<worker name>.service`
+
+          This is only done if this option has a `handlers.journal` field declared.
+
+          To discard all settings declared by this option for each worker and synapse,
+          `lib.mkForce` can be used.
+
+          To discard all settings declared by this option for a single worker or synapse only,
+          [](#opt-services.matrix-synapse.workers._name_.worker_log_config) or
+          [](#opt-services.matrix-synapse.settings.log_config) can be used.
+        '';
+      };
+
       settings = mkOption {
         default = { };
         description = mdDoc ''
@@ -945,23 +993,6 @@ in {
         '';
       }
       {
-        assertion = hasLocalPostgresDB -> config.services.postgresql.enable;
-        message = ''
-          Cannot deploy matrix-synapse with a configuration for a local postgresql database
-            and a missing postgresql service. Since 20.03 it's mandatory to manually configure the
-            database (please read the thread in https://github.com/NixOS/nixpkgs/pull/80447 for
-            further reference).
-
-            If you
-            - try to deploy a fresh synapse, you need to configure the database yourself. An example
-              for this can be found in <nixpkgs/nixos/tests/matrix/synapse.nix>
-            - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true`
-              to your configuration.
-
-          For further information about this update, please read the release-notes of 20.03 carefully.
-        '';
-      }
-      {
         assertion = hasWorkers -> cfg.settings.redis.enabled;
         message = ''
           Workers for matrix-synapse require configuring a redis instance. This can be done
@@ -1002,12 +1033,15 @@ in {
       port = 9093;
     });
 
+    services.matrix-synapse.serviceUnit = if hasWorkers then "matrix-synapse.target" else "matrix-synapse.service";
     services.matrix-synapse.configFile = configFile;
     services.matrix-synapse.package = wrapped;
 
     # default them, so they are additive
     services.matrix-synapse.extras = defaultExtras;
 
+    services.matrix-synapse.log = mapAttrsRecursive (const mkDefault) defaultCommonLogConfig;
+
     users.users.matrix-synapse = {
       group = "matrix-synapse";
       home = cfg.dataDir;
@@ -1034,9 +1068,11 @@ in {
             partOf = [ "matrix-synapse.target" ];
             wantedBy = [ "matrix-synapse.target" ];
             unitConfig.ReloadPropagatedFrom = "matrix-synapse.target";
+            requires = optional hasLocalPostgresDB "postgresql.service";
           }
           else {
             after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
+            requires = optional hasLocalPostgresDB "postgresql.service";
             wantedBy = [ "multi-user.target" ];
           };
         baseServiceConfig = {
@@ -1070,7 +1106,7 @@ in {
             ProtectKernelTunables = true;
             ProtectProc = "invisible";
             ProtectSystem = "strict";
-            ReadWritePaths = [ cfg.dataDir ];
+            ReadWritePaths = [ cfg.dataDir cfg.settings.media_store_path ];
             RemoveIPC = true;
             RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
             RestrictNamespaces = true;
diff --git a/nixos/modules/services/misc/ssm-agent.nix b/nixos/modules/services/misc/amazon-ssm-agent.nix
index d1f371c2bd6..0be79e759c3 100644
--- a/nixos/modules/services/misc/ssm-agent.nix
+++ b/nixos/modules/services/misc/amazon-ssm-agent.nix
@@ -2,7 +2,7 @@
 
 with lib;
 let
-  cfg = config.services.ssm-agent;
+  cfg = config.services.amazon-ssm-agent;
 
   # The SSM agent doesn't pay attention to our /etc/os-release yet, and the lsb-release tool
   # in nixpkgs doesn't seem to work properly on NixOS, so let's just fake the two fields SSM
@@ -16,19 +16,24 @@ let
     esac
   '';
 in {
-  options.services.ssm-agent = {
-    enable = mkEnableOption (lib.mdDoc "AWS SSM agent");
+  imports = [
+    (mkRenamedOptionModule [ "services" "ssm-agent" "enable" ] [ "services" "amazon-ssm-agent" "enable" ])
+    (mkRenamedOptionModule [ "services" "ssm-agent" "package" ] [ "services" "amazon-ssm-agent" "package" ])
+  ];
+
+  options.services.amazon-ssm-agent = {
+    enable = mkEnableOption (lib.mdDoc "Amazon SSM agent");
 
     package = mkOption {
       type = types.path;
-      description = lib.mdDoc "The SSM agent package to use";
-      default = pkgs.ssm-agent.override { overrideEtc = false; };
-      defaultText = literalExpression "pkgs.ssm-agent.override { overrideEtc = false; }";
+      description = lib.mdDoc "The Amazon SSM agent package to use";
+      default = pkgs.amazon-ssm-agent.override { overrideEtc = false; };
+      defaultText = literalExpression "pkgs.amazon-ssm-agent.override { overrideEtc = false; }";
     };
   };
 
   config = mkIf cfg.enable {
-    systemd.services.ssm-agent = {
+    systemd.services.amazon-ssm-agent = {
       inherit (cfg.package.meta) description;
       after    = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/misc/atuin.nix b/nixos/modules/services/misc/atuin.nix
index 8d2c1b5242f..2d6ffc510ce 100644
--- a/nixos/modules/services/misc/atuin.nix
+++ b/nixos/modules/services/misc/atuin.nix
@@ -73,9 +73,7 @@ in
       enable = true;
       ensureUsers = [{
         name = "atuin";
-        ensurePermissions = {
-          "DATABASE atuin" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       }];
       ensureDatabases = [ "atuin" ];
     };
diff --git a/nixos/modules/services/misc/autofs.nix b/nixos/modules/services/misc/autofs.nix
index 55ab15ff003..723b67e8bb6 100644
--- a/nixos/modules/services/misc/autofs.nix
+++ b/nixos/modules/services/misc/autofs.nix
@@ -74,7 +74,7 @@ in
 
   config = mkIf cfg.enable {
 
-    boot.kernelModules = [ "autofs4" ];
+    boot.kernelModules = [ "autofs" ];
 
     systemd.services.autofs =
       { description = "Automounts filesystems on demand";
diff --git a/nixos/modules/services/misc/confd.nix b/nixos/modules/services/misc/confd.nix
index 17c1be57ccb..17c1be57ccb 100755..100644
--- a/nixos/modules/services/misc/confd.nix
+++ b/nixos/modules/services/misc/confd.nix
diff --git a/nixos/modules/services/misc/dysnomia.nix b/nixos/modules/services/misc/dysnomia.nix
index 0f92265ccbe..129345e3810 100644
--- a/nixos/modules/services/misc/dysnomia.nix
+++ b/nixos/modules/services/misc/dysnomia.nix
@@ -223,7 +223,7 @@ in
       ejabberdUser = config.services.ejabberd.user;
     }; }
     // lib.optionalAttrs (config.services.mysql.enable) { mysql-database = {
-        mysqlPort = config.services.mysql.port;
+        mysqlPort = config.services.mysql.settings.mysqld.port;
         mysqlSocket = "/run/mysqld/mysqld.sock";
       } // lib.optionalAttrs cfg.enableAuthentication {
         mysqlUsername = "root";
diff --git a/nixos/modules/services/misc/forgejo.md b/nixos/modules/services/misc/forgejo.md
new file mode 100644
index 00000000000..3df8bc20976
--- /dev/null
+++ b/nixos/modules/services/misc/forgejo.md
@@ -0,0 +1,79 @@
+# Forgejo {#module-forgejo}
+
+Forgejo is a soft-fork of gitea, with strong community focus, as well
+as on self-hosting and federation. [Codeberg](https://codeberg.org) is
+deployed from it.
+
+See [upstream docs](https://forgejo.org/docs/latest/).
+
+The method of choice for running forgejo is using [`services.forgejo`](#opt-services.forgejo.enable).
+
+::: {.warning}
+Running forgejo using `services.gitea.package = pkgs.forgejo` is no longer
+recommended.
+If you experience issues with your instance using `services.gitea`,
+**DO NOT** report them to the `services.gitea` module maintainers.
+**DO** report them to the `services.forgejo` module maintainers instead.
+:::
+
+## Migration from Gitea {#module-forgejo-migration-gitea}
+
+::: {.note}
+Migrating is, while not strictly necessary at this point, highly recommended.
+Both modules and projects are likely to divide further with each release.
+Which might lead to an even more involved migration.
+:::
+
+### Full-Migration {#module-forgejo-migration-gitea-default}
+
+This will migrate the state directory (data), rename and chown the database and
+delete the gitea user.
+
+::: {.note}
+This will also change the git remote ssh-url user from `gitea@` to `forgejo@`,
+when using the host's openssh server (default) instead of the integrated one.
+:::
+
+Instructions for PostgreSQL (default). Adapt accordingly for other databases:
+
+```sh
+systemctl stop gitea
+mv /var/lib/gitea /var/lib/forgejo
+runuser -u postgres -- psql -c '
+  ALTER USER gitea RENAME TO forgejo;
+  ALTER DATABASE gitea RENAME TO forgejo;
+'
+nixos-rebuild switch
+systemctl stop forgejo
+chown -R forgejo:forgejo /var/lib/forgejo
+systemctl restart forgejo
+```
+
+### Alternatively, keeping the gitea user {#module-forgejo-migration-gitea-impersonate}
+
+Alternatively, instead of renaming the database, copying the state folder and
+changing the user, the forgejo module can be set up to re-use the old storage
+locations and database, instead of having to copy or rename them.
+Make sure to disable `services.gitea`, when doing this.
+
+```nix
+services.gitea.enable = false;
+
+services.forgejo = {
+  enable = true;
+  user = "gitea";
+  group = "gitea";
+  stateDir = "/var/lib/gitea";
+  database.name = "gitea";
+  database.user = "gitea";
+};
+
+users.users.gitea = {
+  home = "/var/lib/gitea";
+  useDefaultShell = true;
+  group = "gitea";
+  isSystemUser = true;
+};
+
+users.groups.gitea = {};
+```
diff --git a/nixos/modules/services/misc/forgejo.nix b/nixos/modules/services/misc/forgejo.nix
index f26658b7bcb..454febda589 100644
--- a/nixos/modules/services/misc/forgejo.nix
+++ b/nixos/modules/services/misc/forgejo.nix
@@ -357,6 +357,14 @@ in
         assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
         message = "services.forgejo.database.user must match services.forgejo.user if the database is to be automatically provisioned";
       }
+      { assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.forgejo.createDatabase` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
     ];
 
     services.forgejo.settings = {
@@ -423,7 +431,7 @@ in
       ensureUsers = [
         {
           name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
@@ -621,6 +629,8 @@ in
       };
     };
 
+    services.openssh.settings.AcceptEnv = mkIf (!cfg.settings.START_SSH_SERVER or false) "GIT_PROTOCOL";
+
     users.users = mkIf (cfg.user == "forgejo") {
       forgejo = {
         home = cfg.stateDir;
@@ -664,5 +674,6 @@ in
     };
   };
 
+  meta.doc = ./forgejo.md;
   meta.maintainers = with lib.maintainers; [ bendlas emilylange ];
 }
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index f6ef2bb9191..be528a29899 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -246,6 +246,13 @@ in
         description = lib.mdDoc "Path to a file containing the SMTP password.";
       };
 
+      metricsTokenFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/secrets/gitea/metrics_token";
+        description = lib.mdDoc "Path to a file containing the metrics authentication token.";
+      };
+
       settings = mkOption {
         default = {};
         description = lib.mdDoc ''
@@ -387,6 +394,14 @@ in
       { assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
         message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
       }
+      { assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.gitea.createDatabase` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
     ];
 
     services.gitea.settings = {
@@ -433,6 +448,10 @@ in
         PASSWD = "#mailerpass#";
       };
 
+      metrics = mkIf (cfg.metricsTokenFile != null) {
+        TOKEN = "#metricstoken#";
+      };
+
       oauth2 = {
         JWT_SECRET = "#oauth2jwtsecret#";
       };
@@ -450,7 +469,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
@@ -559,6 +578,10 @@ in
             ${lib.optionalString (cfg.mailerPasswordFile != null) ''
               ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
             ''}
+
+            ${lib.optionalString (cfg.metricsTokenFile != null) ''
+              ${replaceSecretBin} '#metricstoken#' '${cfg.metricsTokenFile}' '${runConfig}'
+            ''}
             chmod u-w '${runConfig}'
           }
           (umask 027; gitea_setup)
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index d607e92e5ec..b73528abaf6 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -154,5 +154,5 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ erictapen bbenno joscha ];
+  meta.maintainers = with lib.maintainers; [ erictapen bbenno ];
 }
diff --git a/nixos/modules/services/misc/gpsd.nix b/nixos/modules/services/misc/gpsd.nix
index ce0f9bb3ba2..5d2e806181d 100644
--- a/nixos/modules/services/misc/gpsd.nix
+++ b/nixos/modules/services/misc/gpsd.nix
@@ -92,6 +92,16 @@ in {
         '';
       };
 
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "-r" "-s" "19200" ];
+        description = lib.mdDoc ''
+          A list of extra command line arguments to pass to gpsd.
+          Check gpsd(8) mangpage for possible arguments.
+        '';
+      };
+
     };
 
   };
@@ -117,12 +127,14 @@ in {
         Type = "forking";
         ExecStart = let
           devices = utils.escapeSystemdExecArgs cfg.devices;
+          extraArgs = utils.escapeSystemdExecArgs cfg.extraArgs;
         in ''
           ${pkgs.gpsd}/sbin/gpsd -D "${toString cfg.debugLevel}"  \
             -S "${toString cfg.port}"                             \
             ${optionalString cfg.readonly "-b"}                   \
             ${optionalString cfg.nowait "-n"}                     \
             ${optionalString cfg.listenany "-G"}                  \
+            ${extraArgs}                                          \
             ${devices}
         '';
       };
diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix
index 67a217c994e..9eb2fdb4659 100644
--- a/nixos/modules/services/misc/klipper.nix
+++ b/nixos/modules/services/misc/klipper.nix
@@ -111,11 +111,11 @@ in
           (submodule {
             options = {
               enable = mkEnableOption (lib.mdDoc ''
-                building of firmware for manual flashing.
+                building of firmware for manual flashing
               '');
               enableKlipperFlash = mkEnableOption (lib.mdDoc ''
                 flashings scripts for firmware. This will add `klipper-flash-$mcu` scripts to your environment which can be called to flash the firmware.
-                Please check the configs at [klipper](https://github.com/Klipper3d/klipper/tree/master/config) whether your board supports flashing via `make flash`.
+                Please check the configs at [klipper](https://github.com/Klipper3d/klipper/tree/master/config) whether your board supports flashing via `make flash`
               '');
               serial = mkOption {
                 type = types.nullOr path;
diff --git a/nixos/modules/services/misc/mediatomb.nix b/nixos/modules/services/misc/mediatomb.nix
index 632b7caaac4..335b1b684b1 100644
--- a/nixos/modules/services/misc/mediatomb.nix
+++ b/nixos/modules/services/misc/mediatomb.nix
@@ -186,7 +186,7 @@ let
   defaultFirewallRules = {
     # udp 1900 port needs to be opened for SSDP (not configurable within
     # mediatomb/gerbera) cf.
-    # http://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup
+    # https://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup
     allowedUDPPorts = [ 1900 cfg.port ];
     allowedTCPPorts = [ cfg.port ];
   };
diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix
index 7e306d718e0..797e145c47a 100644
--- a/nixos/modules/services/misc/moonraker.nix
+++ b/nixos/modules/services/misc/moonraker.nix
@@ -1,8 +1,8 @@
 { config, lib, options, pkgs, ... }:
 with lib;
 let
-  pkg = pkgs.moonraker;
   cfg = config.services.moonraker;
+  pkg = cfg.package;
   opt = options.services.moonraker;
   format = pkgs.formats.ini {
     # https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996
@@ -18,6 +18,14 @@ in {
     services.moonraker = {
       enable = mkEnableOption (lib.mdDoc "Moonraker, an API web server for Klipper");
 
+      package = mkOption {
+        type = with types; nullOr package;
+        default = pkgs.moonraker;
+        defaultText = literalExpression "pkgs.moonraker";
+        example = literalExpression "pkgs.moonraker.override { useGpiod = true; }";
+        description = lib.mdDoc "Moonraker package to use";
+      };
+
       klipperSocket = mkOption {
         type = types.path;
         default = config.services.klipper.apiSocket;
diff --git a/nixos/modules/services/misc/packagekit.nix b/nixos/modules/services/misc/packagekit.nix
index f3e6bf50e9b..5a0d314d25c 100644
--- a/nixos/modules/services/misc/packagekit.nix
+++ b/nixos/modules/services/misc/packagekit.nix
@@ -40,9 +40,9 @@ in
 
   options.services.packagekit = {
     enable = mkEnableOption (lib.mdDoc ''
-      PackageKit provides a cross-platform D-Bus abstraction layer for
+      PackageKit, a cross-platform D-Bus abstraction layer for
       installing software. Software utilizing PackageKit can install
-      software regardless of the package manager.
+      software regardless of the package manager
     '');
 
     settings = mkOption {
diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix
index 74a3b49ac9a..1e0a8d0f928 100644
--- a/nixos/modules/services/misc/paperless.nix
+++ b/nixos/modules/services/misc/paperless.nix
@@ -36,18 +36,7 @@ let
 
   # Secure the services
   defaultServiceConfig = {
-    TemporaryFileSystem = "/:ro";
-    BindReadOnlyPaths = [
-      "/nix/store"
-      "-/etc/resolv.conf"
-      "-/etc/nsswitch.conf"
-      "-/etc/hosts"
-      "-/etc/localtime"
-      "-/etc/ssl/certs"
-      "-/etc/static/ssl/certs"
-      "-/run/postgresql"
-    ] ++ (optional enableRedis redisServer.unixSocket);
-    BindPaths = [
+    ReadWritePaths = [
       cfg.consumptionDir
       cfg.dataDir
       cfg.mediaDir
@@ -66,11 +55,9 @@ let
     PrivateUsers = true;
     ProtectClock = true;
     # Breaks if the home dir of the user is in /home
-    # Also does not add much value in combination with the TemporaryFileSystem.
     # ProtectHome = true;
     ProtectHostname = true;
-    # Would re-mount paths ignored by temporary root
-    #ProtectSystem = "strict";
+    ProtectSystem = "strict";
     ProtectControlGroups = true;
     ProtectKernelLogs = true;
     ProtectKernelModules = true;
@@ -319,17 +306,6 @@ in
         Type = "oneshot";
         # Enable internet access
         PrivateNetwork = false;
-        # Restrict write access
-        BindPaths = [];
-        BindReadOnlyPaths = [
-          "/nix/store"
-          "-/etc/resolv.conf"
-          "-/etc/nsswitch.conf"
-          "-/etc/ssl/certs"
-          "-/etc/static/ssl/certs"
-          "-/etc/hosts"
-          "-/etc/localtime"
-        ];
         ExecStart = let pythonWithNltk = pkg.python.withPackages (ps: [ ps.nltk ]); in ''
           ${pythonWithNltk}/bin/python -m nltk.downloader -d '${nltkDir}' punkt snowball_data stopwords
         '';
@@ -356,12 +332,28 @@ in
       # during migrations
       bindsTo = [ "paperless-scheduler.service" ];
       after = [ "paperless-scheduler.service" ];
+      # Setup PAPERLESS_SECRET_KEY.
+      # If this environment variable is left unset, paperless-ngx defaults
+      # to a well-known value, which is insecure.
+      script = let
+        secretKeyFile = "${cfg.dataDir}/nixos-paperless-secret-key";
+      in ''
+        if [[ ! -f '${secretKeyFile}' ]]; then
+          (
+            umask 0377
+            tr -dc A-Za-z0-9 < /dev/urandom | head -c64 | ${pkgs.moreutils}/bin/sponge '${secretKeyFile}'
+          )
+        fi
+        export PAPERLESS_SECRET_KEY=$(cat '${secretKeyFile}')
+        if [[ ! $PAPERLESS_SECRET_KEY ]]; then
+          echo "PAPERLESS_SECRET_KEY is empty, refusing to start."
+          exit 1
+        fi
+        exec ${pkg.python.pkgs.gunicorn}/bin/gunicorn \
+          -c ${pkg}/lib/paperless-ngx/gunicorn.conf.py paperless.asgi:application
+      '';
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
-        ExecStart = ''
-          ${pkg.python.pkgs.gunicorn}/bin/gunicorn \
-            -c ${pkg}/lib/paperless-ngx/gunicorn.conf.py paperless.asgi:application
-        '';
         Restart = "on-failure";
 
         # gunicorn needs setuid, liblapack needs mbind
@@ -373,7 +365,6 @@ in
         CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
       };
       environment = env // {
-        PATH = mkForce pkg.path;
         PYTHONPATH = "${pkg.python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/paperless-ngx/src";
       };
       # Allow the web interface to access the private /tmp directory of the server.
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index a296fd3816b..20fa71507b6 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -267,7 +267,7 @@ in
       { assertion = cfg.database.passwordFile != null || cfg.database.socket != null;
         message = "one of services.redmine.database.socket or services.redmine.database.passwordFile must be set";
       }
-      { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user && cfg.database.user == cfg.database.name;
         message = "services.redmine.database.user must be set to ${cfg.user} if services.redmine.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.socket != null;
@@ -315,7 +315,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/misc/rkvm.nix b/nixos/modules/services/misc/rkvm.nix
new file mode 100644
index 00000000000..582e8511ed9
--- /dev/null
+++ b/nixos/modules/services/misc/rkvm.nix
@@ -0,0 +1,164 @@
+{ options, config, pkgs, lib, ... }:
+
+with lib;
+let
+  opt = options.services.rkvm;
+  cfg = config.services.rkvm;
+  toml = pkgs.formats.toml { };
+in
+{
+  meta.maintainers = with maintainers; [ ckie ];
+
+  options.services.rkvm = {
+    enable = mkOption {
+      default = cfg.server.enable || cfg.client.enable;
+      defaultText = literalExpression "config.${opt.server.enable} || config.${opt.client.enable}";
+      type = types.bool;
+      description = mdDoc ''
+        Whether to enable rkvm, a Virtual KVM switch for Linux machines.
+      '';
+    };
+
+    package = mkPackageOption pkgs "rkvm" { };
+
+    server = {
+      enable = mkEnableOption "the rkvm server daemon (input transmitter)";
+
+      settings = mkOption {
+        type = types.submodule
+          {
+            freeformType = toml.type;
+            options = {
+              listen = mkOption {
+                type = types.str;
+                default = "0.0.0.0:5258";
+                description = mdDoc ''
+                  An internet socket address to listen on, either IPv4 or IPv6.
+                '';
+              };
+
+              switch-keys = mkOption {
+                type = types.listOf types.str;
+                default = [ "left-alt" "left-ctrl" ];
+                description = mdDoc ''
+                  A key list specifying a host switch combination.
+
+                  _A list of key names is available in <https://github.com/htrefil/rkvm/blob/master/switch-keys.md>._
+                '';
+              };
+
+              certificate = mkOption {
+                type = types.path;
+                default = "/etc/rkvm/certificate.pem";
+                description = mdDoc ''
+                  TLS certificate path.
+
+                  ::: {.note}
+                  This should be generated with {command}`rkvm-certificate-gen`.
+                  :::
+                '';
+              };
+
+              key = mkOption {
+                type = types.path;
+                default = "/etc/rkvm/key.pem";
+                description = mdDoc ''
+                  TLS key path.
+
+                  ::: {.note}
+                  This should be generated with {command}`rkvm-certificate-gen`.
+                  :::
+                '';
+              };
+
+              password = mkOption {
+                type = types.str;
+                description = mdDoc ''
+                  Shared secret token to authenticate the client.
+                  Make sure this matches your client's config.
+                '';
+              };
+            };
+          };
+
+        default = { };
+        description = mdDoc "Structured server daemon configuration";
+      };
+    };
+
+    client = {
+      enable = mkEnableOption "the rkvm client daemon (input receiver)";
+
+      settings = mkOption {
+        type = types.submodule
+          {
+            freeformType = toml.type;
+            options = {
+              server = mkOption {
+                type = types.str;
+                example = "192.168.0.123:5258";
+                description = mdDoc ''
+                  An RKVM server's internet socket address, either IPv4 or IPv6.
+                '';
+              };
+
+              certificate = mkOption {
+                type = types.path;
+                default = "/etc/rkvm/certificate.pem";
+                description = mdDoc ''
+                  TLS ceritficate path.
+
+                  ::: {.note}
+                  This should be generated with {command}`rkvm-certificate-gen`.
+                  :::
+                '';
+              };
+
+              password = mkOption {
+                type = types.str;
+                description = mdDoc ''
+                  Shared secret token to authenticate the client.
+                  Make sure this matches your server's config.
+                '';
+              };
+            };
+          };
+
+        default = {};
+        description = mdDoc "Structured client daemon configuration";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services =
+      let
+        mkBase = component: {
+          description = "RKVM ${component}";
+          wantedBy = [ "multi-user.target" ];
+          after = {
+            server = [ "network.target" ];
+            client = [ "network-online.target" ];
+          }.${component};
+          wants = {
+            server = [ ];
+            client = [ "network-online.target" ];
+          }.${component};
+          serviceConfig = {
+            ExecStart = "${cfg.package}/bin/rkvm-${component} ${toml.generate "rkvm-${component}.toml" cfg.${component}.settings}";
+            Restart = "always";
+            RestartSec = 5;
+            Type = "simple";
+          };
+        };
+      in
+      {
+        rkvm-server = mkIf cfg.server.enable (mkBase "server");
+        rkvm-client = mkIf cfg.client.enable (mkBase "client");
+      };
+  };
+
+}
diff --git a/nixos/modules/services/misc/rshim.nix b/nixos/modules/services/misc/rshim.nix
index 0fef2cc228c..706cf9136b0 100644
--- a/nixos/modules/services/misc/rshim.nix
+++ b/nixos/modules/services/misc/rshim.nix
@@ -12,7 +12,7 @@ let
 in
 {
   options.services.rshim = {
-    enable = lib.mkEnableOption (lib.mdDoc "User-space rshim driver for the BlueField SoC");
+    enable = lib.mkEnableOption (lib.mdDoc "user-space rshim driver for the BlueField SoC");
 
     package = lib.mkPackageOptionMD pkgs "rshim-user-space" { };
 
diff --git a/nixos/modules/services/misc/soft-serve.nix b/nixos/modules/services/misc/soft-serve.nix
new file mode 100644
index 00000000000..2b63b6bcd86
--- /dev/null
+++ b/nixos/modules/services/misc/soft-serve.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.soft-serve;
+  configFile = format.generate "config.yaml" cfg.settings;
+  format = pkgs.formats.yaml { };
+  docUrl = "https://charm.sh/blog/self-hosted-soft-serve/";
+  stateDir = "/var/lib/soft-serve";
+in
+{
+  options = {
+    services.soft-serve = {
+      enable = mkEnableOption "soft-serve";
+
+      package = mkPackageOption pkgs "soft-serve" { };
+
+      settings = mkOption {
+        type = format.type;
+        default = { };
+        description = mdDoc ''
+          The contents of the configuration file for soft-serve.
+
+          See <${docUrl}>.
+        '';
+        example = literalExpression ''
+          {
+            name = "dadada's repos";
+            log_format = "text";
+            ssh = {
+              listen_addr = ":23231";
+              public_url = "ssh://localhost:23231";
+              max_timeout = 30;
+              idle_timeout = 120;
+            };
+            stats.listen_addr = ":23233";
+          }
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      # The config file has to be inside the state dir
+      "L+ ${stateDir}/config.yaml - - - - ${configFile}"
+    ];
+
+    systemd.services.soft-serve = {
+      description = "Soft Serve git server";
+      documentation = [ docUrl ];
+      requires = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment.SOFT_SERVE_DATA_PATH = stateDir;
+
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        Restart = "always";
+        ExecStart = "${getExe cfg.package} serve";
+        StateDirectory = "soft-serve";
+        WorkingDirectory = stateDir;
+        RuntimeDirectory = "soft-serve";
+        RuntimeDirectoryMode = "0750";
+        ProcSubset = "pid";
+        ProtectProc = "invisible";
+        UMask = "0027";
+        CapabilityBoundingSet = "";
+        ProtectHome = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RemoveIPC = true;
+        PrivateMounts = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation @debug @keyring @module @mount @obsolete @privileged @raw-io @reboot @setuid @swap"
+        ];
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.dadada ];
+}
diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
index 580a009a0ad..f2b09f4bc4b 100644
--- a/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixos/modules/services/misc/sourcehut/default.nix
@@ -25,7 +25,7 @@ let
       || head srvMatch == srv # Include sections for the service being configured
       then v
       # Enable Web links and integrations between services.
-      else if tail srvMatch == [ null ] && elem (head srvMatch) cfg.services
+      else if tail srvMatch == [ null ] && cfg.${head srvMatch}.enable
       then {
         inherit (v) origin;
         # mansrht crashes without it
@@ -38,9 +38,9 @@ let
       # for services needing access to them.
       "builds.sr.ht::worker".buildlogs = "/var/log/sourcehut/buildsrht-worker";
       "git.sr.ht".post-update-script = "/usr/bin/gitsrht-update-hook";
-      "git.sr.ht".repos = "/var/lib/sourcehut/gitsrht/repos";
+      "git.sr.ht".repos = cfg.settings."git.sr.ht".repos;
       "hg.sr.ht".changegroup-script = "/usr/bin/hgsrht-hook-changegroup";
-      "hg.sr.ht".repos = "/var/lib/sourcehut/hgsrht/repos";
+      "hg.sr.ht".repos = cfg.settings."hg.sr.ht".repos;
       # Making this a per service option despite being in a global section,
       # so that it uses the redis-server used by the service.
       "sr.ht".redis-host = cfg.${srv}.redis.host;
@@ -77,6 +77,14 @@ let
       type = types.path;
       apply = s: "<" + toString s;
     };
+    api-origin = mkOption {
+      description = lib.mdDoc "Origin URL for the API";
+      type = types.str;
+      default = "http://${cfg.listenAddress}:${toString (cfg.${srv}.port + 100)}";
+      defaultText = lib.literalMD ''
+        `"http://''${`[](#opt-services.sourcehut.listenAddress)`}:''${toString (`[](#opt-services.sourcehut.${srv}.port)` + 100)}"`
+      '';
+    };
   };
 
   # Specialized python containing all the modules
@@ -112,15 +120,6 @@ in
       and account management services
     '');
 
-    services = mkOption {
-      type = with types; listOf (enum
-        [ "builds" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
-      defaultText = "locally enabled services";
-      description = lib.mdDoc ''
-        Services that may be displayed as links in the title bar of the Web interface.
-      '';
-    };
-
     listenAddress = mkOption {
       type = types.str;
       default = "localhost";
@@ -400,8 +399,8 @@ in
               This setting is propagated to newer and existing repositories.
             '';
             type = types.str;
-            default = "${cfg.python}/bin/hgsrht-hook-changegroup";
-            defaultText = "\${cfg.python}/bin/hgsrht-hook-changegroup";
+            default = "${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup";
+            defaultText = "\${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup";
           };
           repos = mkOption {
             description = lib.mdDoc ''
@@ -438,7 +437,7 @@ in
         };
 
         options."lists.sr.ht" = commonServiceSettings "lists" // {
-          allow-new-lists = mkEnableOption (lib.mdDoc "Allow creation of new lists");
+          allow-new-lists = mkEnableOption (lib.mdDoc "creation of new lists");
           notify-from = mkOption {
             description = lib.mdDoc "Outgoing email for notifications generated by users.";
             type = types.str;
@@ -501,12 +500,6 @@ in
         options."meta.sr.ht" =
           removeAttrs (commonServiceSettings "meta")
             ["oauth-client-id" "oauth-client-secret"] // {
-          api-origin = mkOption {
-            description = lib.mdDoc "Origin URL for API, 100 more than web.";
-            type = types.str;
-            default = "http://${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
-            defaultText = lib.literalMD ''`"http://''${`[](#opt-services.sourcehut.listenAddress)`}:''${toString (`[](#opt-services.sourcehut.meta.port)` + 100)}"`'';
-          };
           webhooks = mkOption {
             description = lib.mdDoc "The Redis connection used for the webhooks worker.";
             type = types.str;
@@ -784,6 +777,7 @@ in
         extraConfig = ''
           PermitUserEnvironment SRHT_*
         '';
+        startWhenNeeded = false;
       };
       environment.etc."ssh/sourcehut/config.ini".source =
         settingsFormat.generate "sourcehut-dispatch-config.ini"
@@ -792,15 +786,28 @@ in
       environment.etc."ssh/sourcehut/subdir/srht-dispatch" = {
         # sshd_config(5): The program must be owned by root, not writable by group or others
         mode = "0755";
-        source = pkgs.writeShellScript "srht-dispatch" ''
+        source = pkgs.writeShellScript "srht-dispatch-wrapper" ''
           set -e
+          set -x
           cd /etc/ssh/sourcehut/subdir
-          ${cfg.python}/bin/gitsrht-dispatch "$@"
+          ${pkgs.sourcehut.gitsrht}/bin/gitsrht-dispatch "$@"
         '';
       };
+      systemd.tmpfiles.settings."10-sourcehut-gitsrht" = mkIf cfg.git.enable (
+        builtins.listToAttrs (map (name: {
+          name = "/var/log/sourcehut/gitsrht-${name}";
+          value.f = {
+            inherit (cfg.git) user group;
+            mode = "0644";
+          };
+        }) [ "keys" "shell" "update-hook" ])
+      );
       systemd.services.sshd = {
-        #path = optional cfg.git.enable [ cfg.git.package ];
+        preStart = mkIf cfg.hg.enable ''
+          chown ${cfg.hg.user}:${cfg.hg.group} /var/log/sourcehut/hgsrht-keys
+        '';
         serviceConfig = {
+          LogsDirectory = "sourcehut";
           BindReadOnlyPaths =
             # Note that those /usr/bin/* paths are hardcoded in multiple places in *.sr.ht,
             # for instance to get the user from the [git.sr.ht::dispatch] settings.
@@ -813,7 +820,6 @@ in
               "${pkgs.writeShellScript "buildsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/buildsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys "$@"
               ''}:/usr/bin/buildsrht-keys"
               "${pkgs.sourcehut.buildsrht}/bin/master-shell:/usr/bin/master-shell"
@@ -825,31 +831,26 @@ in
               "${pkgs.writeShellScript "gitsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/gitsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@"
               ''}:/usr/bin/gitsrht-keys"
               "${pkgs.writeShellScript "gitsrht-shell-wrapper" ''
                 set -e
                 cd /run/sourcehut/gitsrht/subdir
-                set -x
+                export PATH="${cfg.git.package}/bin:$PATH"
+                export SRHT_CONFIG=/run/sourcehut/gitsrht/config.ini
                 exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell "$@"
               ''}:/usr/bin/gitsrht-shell"
               "${pkgs.writeShellScript "gitsrht-update-hook" ''
                 set -e
-                test -e "''${PWD%/*}"/config.ini ||
-                # Git hooks are run relative to their repository's directory,
-                # but gitsrht-update-hook looks up ../config.ini
-                ln -s /run/sourcehut/gitsrht/config.ini "''${PWD%/*}"/config.ini
+                export SRHT_CONFIG=/run/sourcehut/gitsrht/config.ini
                 # hooks/post-update calls /usr/bin/gitsrht-update-hook as hooks/stage-3
                 # but this wrapper being a bash script, it overrides $0 with /usr/bin/gitsrht-update-hook
                 # hence this hack to put hooks/stage-3 back into gitsrht-update-hook's $0
                 if test "''${STAGE3:+set}"
                 then
-                  set -x
                   exec -a hooks/stage-3 ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
                 else
                   export STAGE3=set
-                  set -x
                   exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
                 fi
               ''}:/usr/bin/gitsrht-update-hook"
@@ -860,13 +861,11 @@ in
               "${pkgs.writeShellScript "hgsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/hgsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@"
               ''}:/usr/bin/hgsrht-keys"
               "${pkgs.writeShellScript "hgsrht-shell-wrapper" ''
                 set -e
                 cd /run/sourcehut/hgsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell "$@"
               ''}:/usr/bin/hgsrht-shell"
               # Mercurial's changegroup hooks are run relative to their repository's directory,
@@ -875,8 +874,7 @@ in
                 set -e
                 test -e "''$PWD"/config.ini ||
                 ln -s /run/sourcehut/hgsrht/config.ini "''$PWD"/config.ini
-                set -x
-                exec -a "$0" ${cfg.python}/bin/hgsrht-hook-changegroup "$@"
+                exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup "$@"
               ''}:/usr/bin/hgsrht-hook-changegroup"
             ];
         };
@@ -1066,10 +1064,11 @@ in
           };
         })
       ];
-      extraServices.gitsrht-api = {
-        serviceConfig.Restart = "always";
-        serviceConfig.RestartSec = "5s";
-        serviceConfig.ExecStart = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-api -b ${cfg.listenAddress}:${toString (cfg.git.port + 100)}";
+      extraServices.gitsrht-api.serviceConfig = {
+        Restart = "always";
+        RestartSec = "5s";
+        ExecStart = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-api -b ${cfg.listenAddress}:${toString (cfg.git.port + 100)}";
+        BindPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
       };
       extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
         serviceConfig = {
@@ -1188,7 +1187,7 @@ in
       extraServices.listssrht-lmtp = {
         wants = [ "postfix.service" ];
         unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
-        serviceConfig.ExecStart = "${cfg.python}/bin/listssrht-lmtp";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.listssrht}/bin/listssrht-lmtp";
         # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
         serviceConfig.PrivateUsers = mkForce false;
       };
@@ -1252,55 +1251,30 @@ in
           ) cfg.settings));
         serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b ${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
       };
-      extraConfig = mkMerge [
-        {
-          assertions = [
-            { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
-                          s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
-              message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
-            }
-          ];
-          environment.systemPackages = optional cfg.meta.enable
-            (pkgs.writeShellScriptBin "metasrht-manageuser" ''
-              set -eux
-              if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
-              then exec sudo -u '${cfg.meta.user}' "$0" "$@"
-              else
-                # In order to load config.ini
-                if cd /run/sourcehut/metasrht
-                then exec ${cfg.python}/bin/metasrht-manageuser "$@"
-                else cat <<EOF
-                  Please run: sudo systemctl start metasrht
-              EOF
-                  exit 1
-                fi
+      extraConfig = {
+        assertions = [
+          { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
+                        s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
+            message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
+          }
+        ];
+        environment.systemPackages = optional cfg.meta.enable
+          (pkgs.writeShellScriptBin "metasrht-manageuser" ''
+            set -eux
+            if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
+            then exec sudo -u '${cfg.meta.user}' "$0" "$@"
+            else
+              # In order to load config.ini
+              if cd /run/sourcehut/metasrht
+              then exec ${pkgs.sourcehut.metasrht}/bin/metasrht-manageuser "$@"
+              else cat <<EOF
+                Please run: sudo systemctl start metasrht
+            EOF
+                exit 1
               fi
-            '');
-        }
-        (mkIf cfg.nginx.enable {
-          services.nginx.virtualHosts."meta.${domain}" = {
-            locations."/query" = {
-              proxyPass = cfg.settings."meta.sr.ht".api-origin;
-              extraConfig = ''
-                if ($request_method = 'OPTIONS') {
-                  add_header 'Access-Control-Allow-Origin' '*';
-                  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-                  add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-                  add_header 'Access-Control-Max-Age' 1728000;
-                  add_header 'Content-Type' 'text/plain; charset=utf-8';
-                  add_header 'Content-Length' 0;
-                  return 204;
-                }
-
-                add_header 'Access-Control-Allow-Origin' '*';
-                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-                add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
-              '';
-            };
-          };
-        })
-      ];
+            fi
+          '');
+      };
     })
 
     (import ./service.nix "pages" {
@@ -1356,7 +1330,7 @@ in
       extraServices.todosrht-lmtp = {
         wants = [ "postfix.service" ];
         unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
-        serviceConfig.ExecStart = "${cfg.python}/bin/todosrht-lmtp";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.todosrht}/bin/todosrht-lmtp";
         # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
         serviceConfig.PrivateUsers = mkForce false;
       };
@@ -1388,6 +1362,10 @@ in
         dispatch is deprecated. See https://sourcehut.org/blog/2022-08-01-dispatch-deprecation-plans/
         for more information.
     '')
+
+    (mkRemovedOptionModule [ "services" "sourcehut" "services"] ''
+        This option was removed in favor of individual <service>.enable flags.
+    '')
   ];
 
   meta.doc = ./default.md;
diff --git a/nixos/modules/services/misc/sourcehut/service.nix b/nixos/modules/services/misc/sourcehut/service.nix
index aae13e0cc2c..f08d5eb4687 100644
--- a/nixos/modules/services/misc/sourcehut/service.nix
+++ b/nixos/modules/services/misc/sourcehut/service.nix
@@ -108,7 +108,7 @@ let
       #SocketBindDeny = "any";
       SystemCallFilter = [
         "@system-service"
-        "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@timer"
+        "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@timer"
         "@chown" "@setuid"
       ];
       SystemCallArchitectures = "native";
@@ -222,6 +222,23 @@ in
             expires 30d;
           '';
         };
+        locations."/query" = mkIf (cfg.settings.${iniKey} ? api-origin) {
+          proxyPass = cfg.settings.${iniKey}.api-origin;
+          extraConfig = ''
+            add_header 'Access-Control-Allow-Origin' '*';
+            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+            add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+
+            if ($request_method = 'OPTIONS') {
+              add_header 'Access-Control-Max-Age' 1728000;
+              add_header 'Content-Type' 'text/plain; charset=utf-8';
+              add_header 'Content-Length' 0;
+              return 204;
+            }
+
+            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
+          '';
+        };
       } cfg.nginx.virtualHost ];
     };
 
@@ -232,12 +249,12 @@ in
       ensureDatabases = [ srvCfg.postgresql.database ];
       ensureUsers = map (name: {
           inherit name;
-          ensurePermissions = { "DATABASE \"${srvCfg.postgresql.database}\"" = "ALL PRIVILEGES"; };
+          # We don't use it because we have a special default database name with dots.
+          # TODO(for maintainers of sourcehut): migrate away from custom preStart script.
+          ensureDBOwnership = false;
         }) [srvCfg.user];
     };
 
-    services.sourcehut.services = mkDefault (filter (s: cfg.${s}.enable)
-      [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
 
     services.sourcehut.settings = mkMerge [
       {
@@ -363,6 +380,21 @@ in
         }
         extraService
       ])) extraServices)
+
+      # Work around 'pq: permission denied for schema public' with postgres v15.
+      # See https://github.com/NixOS/nixpkgs/issues/216989
+      # Workaround taken from nixos/forgejo: https://github.com/NixOS/nixpkgs/pull/262741
+      # TODO(to maintainers of sourcehut): please migrate away from this workaround
+      # by migrating away from database name defaults with dots.
+      (lib.mkIf (
+          cfg.postgresql.enable
+          && lib.strings.versionAtLeast config.services.postgresql.package.version "15.0"
+        ) {
+          postgresql.postStart = (lib.mkAfter ''
+            $PSQL -tAc 'ALTER DATABASE "${srvCfg.postgresql.database}" OWNER TO "${srvCfg.user}";'
+          '');
+        }
+      )
     ];
 
     systemd.timers = mapAttrs (timerName: timer:
diff --git a/nixos/modules/services/misc/spice-autorandr.nix b/nixos/modules/services/misc/spice-autorandr.nix
new file mode 100644
index 00000000000..8437441c752
--- /dev/null
+++ b/nixos/modules/services/misc/spice-autorandr.nix
@@ -0,0 +1,26 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.spice-autorandr;
+in
+{
+  options = {
+    services.spice-autorandr = {
+      enable = lib.mkEnableOption (lib.mdDoc "spice-autorandr service that will automatically resize display to match SPICE client window size.");
+      package = lib.mkPackageOptionMD pkgs "spice-autorandr" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.user.services.spice-autorandr = {
+      wantedBy = [ "default.target" ];
+      after = [ "spice-vdagentd.service" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/spice-autorandr";
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/tp-auto-kbbl.nix b/nixos/modules/services/misc/tp-auto-kbbl.nix
index 8d92d3d9367..1076c814e86 100644
--- a/nixos/modules/services/misc/tp-auto-kbbl.nix
+++ b/nixos/modules/services/misc/tp-auto-kbbl.nix
@@ -9,7 +9,7 @@ in {
 
   options = {
     services.tp-auto-kbbl = {
-      enable = mkEnableOption (lib.mdDoc "Auto toggle keyboard back-lighting on Thinkpads (and maybe other laptops) for Linux");
+      enable = mkEnableOption (lib.mdDoc "auto toggle keyboard back-lighting on Thinkpads (and maybe other laptops) for Linux");
 
       package = mkOption {
         type = types.package;
diff --git a/nixos/modules/services/misc/xmr-stak.nix b/nixos/modules/services/misc/xmr-stak.nix
index 6e123cf0380..54efae48d5d 100644
--- a/nixos/modules/services/misc/xmr-stak.nix
+++ b/nixos/modules/services/misc/xmr-stak.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.xmr-stak;
 
   pkg = pkgs.xmr-stak.override {
-    inherit (cfg) openclSupport cudaSupport;
+    inherit (cfg) openclSupport;
   };
 
 in
@@ -17,7 +17,6 @@ in
     services.xmr-stak = {
       enable = mkEnableOption (lib.mdDoc "xmr-stak miner");
       openclSupport = mkEnableOption (lib.mdDoc "support for OpenCL (AMD/ATI graphics cards)");
-      cudaSupport = mkEnableOption (lib.mdDoc "support for CUDA (NVidia graphics cards)");
 
       extraArgs = mkOption {
         type = types.listOf types.str;
@@ -64,15 +63,12 @@ in
       wantedBy = [ "multi-user.target" ];
       bindsTo = [ "network-online.target" ];
       after = [ "network-online.target" ];
-      environment = mkIf cfg.cudaSupport {
-        LD_LIBRARY_PATH = "${pkgs.linuxPackages_latest.nvidia_x11}/lib";
-      };
 
       preStart = concatStrings (flip mapAttrsToList cfg.configFiles (fn: content: ''
         ln -sf '${pkgs.writeText "xmr-stak-${fn}" content}' '${fn}'
       ''));
 
-      serviceConfig = let rootRequired = cfg.openclSupport || cfg.cudaSupport; in {
+      serviceConfig = let rootRequired = cfg.openclSupport; in {
         ExecStart = "${pkg}/bin/xmr-stak ${concatStringsSep " " cfg.extraArgs}";
         # xmr-stak generates cpu and/or gpu configuration files
         WorkingDirectory = "/tmp";
diff --git a/nixos/modules/services/misc/xmrig.nix b/nixos/modules/services/misc/xmrig.nix
index d2aa3df45d5..f75b47ffece 100644
--- a/nixos/modules/services/misc/xmrig.nix
+++ b/nixos/modules/services/misc/xmrig.nix
@@ -52,15 +52,15 @@ with lib;
   };
 
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "msr" ];
+    hardware.cpu.x86.msr.enable = true;
 
     systemd.services.xmrig = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       description = "XMRig Mining Software Service";
       serviceConfig = {
-        ExecStartPre = "${cfg.package}/bin/xmrig --config=${configFile} --dry-run";
-        ExecStart = "${cfg.package}/bin/xmrig --config=${configFile}";
+        ExecStartPre = "${lib.getExe cfg.package} --config=${configFile} --dry-run";
+        ExecStart = "${lib.getExe cfg.package} --config=${configFile}";
         # https://xmrig.com/docs/miner/randomx-optimization-guide/msr
         # If you use recent XMRig with root privileges (Linux) or admin
         # privileges (Windows) the miner configure all MSR registers
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index b2e4e760d82..fca03b2ad4e 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -67,14 +67,14 @@ in {
   options = {
     services.zoneminder = with lib; {
       enable = lib.mkEnableOption (lib.mdDoc ''
-        ZoneMinder
+        ZoneMinder.
 
         If you intend to run the database locally, you should set
         `config.services.zoneminder.database.createLocally` to true. Otherwise,
         when set to `false` (the default), you will have to create the database
         and database user as well as populate the database yourself.
         Additionally, you will need to run `zmupdate.pl` yourself when
-        upgrading to a newer version.
+        upgrading to a newer version
       '');
 
       webserver = mkOption {
diff --git a/nixos/modules/services/monitoring/bosun.nix b/nixos/modules/services/monitoring/bosun.nix
index dc75fda6ed8..1dc19743461 100644
--- a/nixos/modules/services/monitoring/bosun.nix
+++ b/nixos/modules/services/monitoring/bosun.nix
@@ -108,7 +108,7 @@ in {
           option.
 
           A detailed description of the supported syntax can be found at-spi2-atk
-          http://bosun.org/configuration.html
+          https://bosun.org/configuration.html
         '';
       };
 
diff --git a/nixos/modules/services/monitoring/certspotter.md b/nixos/modules/services/monitoring/certspotter.md
new file mode 100644
index 00000000000..9bf6e1d946a
--- /dev/null
+++ b/nixos/modules/services/monitoring/certspotter.md
@@ -0,0 +1,74 @@
+# Cert Spotter {#module-services-certspotter}
+
+Cert Spotter is a tool for monitoring [Certificate Transparency](https://en.wikipedia.org/wiki/Certificate_Transparency)
+logs.
+
+## Service Configuration {#modules-services-certspotter-service-configuration}
+
+A basic config that notifies you of all certificate changes for your
+domain would look as follows:
+
+```nix
+services.certspotter = {
+  enable = true;
+  # replace example.org with your domain name
+  watchlist = [ ".example.org" ];
+  emailRecipients = [ "webmaster@example.org" ];
+};
+
+# Configure an SMTP client
+programs.msmtp.enable = true;
+# Or you can use any other module that provides sendmail, like
+# services.nullmailer, services.opensmtpd, services.postfix
+```
+
+In this case, the leading dot in `".example.org"` means that Cert
+Spotter should monitor not only `example.org`, but also all of its
+subdomains.
+
+## Operation {#modules-services-certspotter-operation}
+
+**By default, NixOS configures Cert Spotter to skip all certificates
+issued before its first launch**, because checking the entire
+Certificate Transparency logs requires downloading tens of terabytes of
+data. If you want to check the *entire* logs for previously issued
+certificates, you have to set `services.certspotter.startAtEnd` to
+`false` and remove all previously saved log state in
+`/var/lib/certspotter/logs`. The downloaded logs aren't saved, so if you
+add a new domain to the watchlist and want Cert Spotter to go through
+the logs again, you will have to remove `/var/lib/certspotter/logs`
+again.
+
+After catching up with the logs, Cert Spotter will start monitoring live
+logs. As of October 2023, it uses around **20 Mbps** of traffic on
+average.
+
+## Hooks {#modules-services-certspotter-hooks}
+
+Cert Spotter supports running custom hooks instead of (or in addition
+to) sending emails. Hooks are shell scripts that will be passed certain
+environment variables.
+
+To see hook documentation, see Cert Spotter's man pages:
+
+```ShellSession
+nix-shell -p certspotter --run 'man 8 certspotter-script'
+```
+
+For example, you can remove `emailRecipients` and send email
+notifications manually using the following hook:
+
+```nix
+services.certspotter.hooks = [
+  (pkgs.writeShellScript "certspotter-hook" ''
+    function print_email() {
+      echo "Subject: [certspotter] $SUMMARY"
+      echo "Mime-Version: 1.0"
+      echo "Content-Type: text/plain; charset=US-ASCII"
+      echo
+      cat "$TEXT_FILENAME"
+    }
+    print_email | ${config.services.certspotter.sendmailPath} -i webmaster@example.org
+  '')
+];
+```
diff --git a/nixos/modules/services/monitoring/certspotter.nix b/nixos/modules/services/monitoring/certspotter.nix
new file mode 100644
index 00000000000..aafa29daa87
--- /dev/null
+++ b/nixos/modules/services/monitoring/certspotter.nix
@@ -0,0 +1,143 @@
+{ config
+, lib
+, pkgs
+, ... }:
+
+let
+  cfg = config.services.certspotter;
+
+  configDir = pkgs.linkFarm "certspotter-config" (
+    lib.toList {
+      name = "watchlist";
+      path = pkgs.writeText "certspotter-watchlist" (builtins.concatStringsSep "\n" cfg.watchlist);
+    }
+    ++ lib.optional (cfg.emailRecipients != [ ]) {
+      name = "email_recipients";
+      path = pkgs.writeText "certspotter-email_recipients" (builtins.concatStringsSep "\n" cfg.emailRecipients);
+    }
+    # always generate hooks dir when no emails are provided to allow running cert spotter with no hooks/emails
+    ++ lib.optional (cfg.emailRecipients == [ ] || cfg.hooks != [ ]) {
+      name = "hooks.d";
+      path = pkgs.linkFarm "certspotter-hooks" (lib.imap1 (i: path: {
+        inherit path;
+        name = "hook${toString i}";
+      }) cfg.hooks);
+    });
+in
+{
+  options.services.certspotter = {
+    enable = lib.mkEnableOption "Cert Spotter, a Certificate Transparency log monitor";
+
+    package = lib.mkPackageOptionMD pkgs "certspotter" { };
+
+    startAtEnd = lib.mkOption {
+      type = lib.types.bool;
+      description = ''
+        Whether to skip certificates issued before the first launch of Cert Spotter.
+        Setting this to `false` will cause Cert Spotter to download tens of terabytes of data.
+      '';
+      default = true;
+    };
+
+    sendmailPath = lib.mkOption {
+      type = with lib.types; nullOr path;
+      description = ''
+        Path to the `sendmail` binary. By default, the local sendmail wrapper is used
+        (see {option}`services.mail.sendmailSetuidWrapper`}).
+      '';
+      example = lib.literalExpression ''"''${pkgs.system-sendmail}/bin/sendmail"'';
+    };
+
+    watchlist = lib.mkOption {
+      type = with lib.types; listOf str;
+      description = "Domain names to watch. To monitor a domain with all subdomains, prefix its name with `.` (e.g. `.example.org`).";
+      default = [ ];
+      example = [ ".example.org" "another.example.com" ];
+    };
+
+    emailRecipients = lib.mkOption {
+      type = with lib.types; listOf str;
+      description = "A list of email addresses to send certificate updates to.";
+      default = [ ];
+    };
+
+    hooks = lib.mkOption {
+      type = with lib.types; listOf path;
+      description = ''
+        Scripts to run upon the detection of a new certificate. See `man 8 certspotter-script` or
+        [the GitHub page](https://github.com/SSLMate/certspotter/blob/${pkgs.certspotter.src.rev or "master"}/man/certspotter-script.md)
+        for more info.
+      '';
+      default = [ ];
+      example = lib.literalExpression ''
+        [
+          (pkgs.writeShellScript "certspotter-hook" '''
+            echo "Event summary: $SUMMARY."
+          ''')
+        ]
+      '';
+    };
+
+    extraFlags = lib.mkOption {
+      type = with lib.types; listOf str;
+      description = "Extra command-line arguments to pass to Cert Spotter";
+      example = [ "-no_save" ];
+      default = [ ];
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.emailRecipients != [ ]) -> (cfg.sendmailPath != null);
+        message = ''
+          You must configure the sendmail setuid wrapper (services.mail.sendmailSetuidWrapper)
+          or services.certspotter.sendmailPath
+        '';
+      }
+    ];
+
+    services.certspotter.sendmailPath = let
+      inherit (config.security) wrapperDir;
+      inherit (config.services.mail) sendmailSetuidWrapper;
+    in lib.mkMerge [
+      (lib.mkIf (sendmailSetuidWrapper != null) (lib.mkOptionDefault "${wrapperDir}/${sendmailSetuidWrapper.program}"))
+      (lib.mkIf (sendmailSetuidWrapper == null) (lib.mkOptionDefault null))
+    ];
+
+    users.users.certspotter = {
+      description = "Cert Spotter user";
+      group = "certspotter";
+      home = "/var/lib/certspotter";
+      isSystemUser = true;
+    };
+    users.groups.certspotter = { };
+
+    systemd.services.certspotter = {
+      description = "Cert Spotter - Certificate Transparency Monitor";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.CERTSPOTTER_CONFIG_DIR = configDir;
+      environment.SENDMAIL_PATH = if cfg.sendmailPath != null then cfg.sendmailPath else "/run/current-system/sw/bin/false";
+      script = ''
+        export CERTSPOTTER_STATE_DIR="$STATE_DIRECTORY"
+        cd "$CERTSPOTTER_STATE_DIR"
+        ${lib.optionalString cfg.startAtEnd ''
+          if [[ ! -d logs ]]; then
+            # Don't download certificates issued before the first launch
+            exec ${cfg.package}/bin/certspotter -start_at_end ${lib.escapeShellArgs cfg.extraFlags}
+          fi
+        ''}
+        exec ${cfg.package}/bin/certspotter ${lib.escapeShellArgs cfg.extraFlags}
+      '';
+      serviceConfig = {
+        User = "certspotter";
+        Group = "certspotter";
+        StateDirectory = "certspotter";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ chayleaf ];
+  meta.doc = ./certspotter.md;
+}
diff --git a/nixos/modules/services/monitoring/goss.md b/nixos/modules/services/monitoring/goss.md
new file mode 100644
index 00000000000..1e636aa3bdf
--- /dev/null
+++ b/nixos/modules/services/monitoring/goss.md
@@ -0,0 +1,44 @@
+# Goss {#module-services-goss}
+
+[goss](https://goss.rocks/) is a YAML based serverspec alternative tool
+for validating a server's configuration.
+
+## Basic Usage {#module-services-goss-basic-usage}
+
+A minimal configuration looks like this:
+
+```
+{
+  services.goss = {
+    enable = true;
+
+    environment = {
+      GOSS_FMT = "json";
+      GOSS_LOGLEVEL = "TRACE";
+    };
+
+    settings = {
+      addr."tcp://localhost:8080" = {
+        reachable = true;
+        local-address = "127.0.0.1";
+      };
+      command."check-goss-version" = {
+        exec = "${lib.getExe pkgs.goss} --version";
+        exit-status = 0;
+      };
+      dns.localhost.resolvable = true;
+      file."/nix" = {
+        filetype = "directory";
+        exists = true;
+      };
+      group.root.exists = true;
+      kernel-param."kernel.ostype".value = "Linux";
+      service.goss = {
+        enabled = true;
+        running = true;
+      };
+      user.root.exists = true;
+    };
+  };
+}
+```
diff --git a/nixos/modules/services/monitoring/goss.nix b/nixos/modules/services/monitoring/goss.nix
new file mode 100644
index 00000000000..64a8dad0703
--- /dev/null
+++ b/nixos/modules/services/monitoring/goss.nix
@@ -0,0 +1,86 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.goss;
+
+  settingsFormat = pkgs.formats.yaml { };
+  configFile = settingsFormat.generate "goss.yaml" cfg.settings;
+
+in {
+  meta = {
+    doc = ./goss.md;
+    maintainers = [ lib.maintainers.anthonyroussel ];
+  };
+
+  options = {
+    services.goss = {
+      enable = lib.mkEnableOption (lib.mdDoc "Goss daemon");
+
+      package = lib.mkPackageOptionMD pkgs "goss" { };
+
+      environment = lib.mkOption {
+        type = lib.types.attrsOf lib.types.str;
+        default = { };
+        example = {
+          GOSS_FMT = "json";
+          GOSS_LOGLEVEL = "FATAL";
+          GOSS_LISTEN = ":8080";
+        };
+        description = lib.mdDoc ''
+          Environment variables to set for the goss service.
+
+          See <https://github.com/goss-org/goss/blob/master/docs/manual.md>
+        '';
+      };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule { freeformType = settingsFormat.type; };
+        default = { };
+        example = {
+          addr."tcp://localhost:8080" = {
+            reachable = true;
+            local-address = "127.0.0.1";
+          };
+          service.goss = {
+            enabled = true;
+            running = true;
+          };
+        };
+        description = lib.mdDoc ''
+          The global options in `config` file in yaml format.
+
+          Refer to <https://github.com/goss-org/goss/blob/master/docs/goss-json-schema.yaml> for schema.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.goss = {
+      description = "Goss - Quick and Easy server validation";
+      unitConfig.Documentation = "https://github.com/goss-org/goss/blob/master/docs/manual.md";
+
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+
+      environment = {
+        GOSS_FILE = configFile;
+      } // cfg.environment;
+
+      reloadTriggers = [ configFile ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStart = "${cfg.package}/bin/goss serve";
+        Group = "goss";
+        Restart = "on-failure";
+        RestartSec = 5;
+        User = "goss";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/grafana-image-renderer.nix b/nixos/modules/services/monitoring/grafana-image-renderer.nix
index 36258866646..afe9eb4d7b9 100644
--- a/nixos/modules/services/monitoring/grafana-image-renderer.nix
+++ b/nixos/modules/services/monitoring/grafana-image-renderer.nix
@@ -108,7 +108,7 @@ in {
 
     services.grafana.settings.rendering = mkIf cfg.provisionGrafana {
       server_url = "http://localhost:${toString cfg.settings.service.port}/render";
-      callback_url = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
+      callback_url = "http://${config.services.grafana.settings.server.http_addr}:${toString config.services.grafana.settings.server.http_port}";
     };
 
     services.grafana-image-renderer.chromium = mkDefault pkgs.chromium;
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index e90a0e9d16d..f84d677f14d 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -88,7 +88,7 @@ let
   # Get a submodule without any embedded metadata:
   _filter = x: filterAttrs (k: v: k != "_module") x;
 
-  # http://docs.grafana.org/administration/provisioning/#datasources
+  # https://grafana.com/docs/grafana/latest/administration/provisioning/#datasources
   grafanaTypes.datasourceConfig = types.submodule {
     freeformType = provisioningSettingsFormat.type;
 
@@ -140,7 +140,7 @@ let
     };
   };
 
-  # http://docs.grafana.org/administration/provisioning/#dashboards
+  # https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards
   grafanaTypes.dashboardConfig = types.submodule {
     freeformType = provisioningSettingsFormat.type;
 
@@ -1841,6 +1841,7 @@ in
       serviceConfig = {
         WorkingDirectory = cfg.dataDir;
         User = "grafana";
+        Restart = "on-failure";
         RuntimeDirectory = "grafana";
         RuntimeDirectoryMode = "0755";
         # Hardening
diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix
index 65c91b8f79b..cc3d7097620 100644
--- a/nixos/modules/services/monitoring/graphite.nix
+++ b/nixos/modules/services/monitoring/graphite.nix
@@ -102,7 +102,7 @@ in {
         default = "";
         description = lib.mdDoc ''
           Graphite webapp settings. See:
-          <http://graphite.readthedocs.io/en/latest/config-local-settings.html>
+          <https://graphite.readthedocs.io/en/latest/config-local-settings.html>
         '';
       };
     };
diff --git a/nixos/modules/services/monitoring/librenms.nix b/nixos/modules/services/monitoring/librenms.nix
new file mode 100644
index 00000000000..08a46754e0e
--- /dev/null
+++ b/nixos/modules/services/monitoring/librenms.nix
@@ -0,0 +1,624 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.librenms;
+  settingsFormat = pkgs.formats.json {};
+  configJson = settingsFormat.generate "librenms-config.json" cfg.settings;
+
+  package = pkgs.librenms.override {
+    logDir = cfg.logDir;
+    dataDir = cfg.dataDir;
+  };
+
+  phpOptions = ''
+    log_errors = on
+    post_max_size = 100M
+    upload_max_filesize = 100M
+    date.timezone = "${config.time.timeZone}"
+  '';
+  phpIni = pkgs.runCommand "php.ini" {
+    inherit (package) phpPackage;
+    inherit phpOptions;
+    preferLocalBuild = true;
+    passAsFile = [ "phpOptions" ];
+  } ''
+    cat $phpPackage/etc/php.ini $phpOptionsPath > $out
+  '';
+
+  artisanWrapper = pkgs.writeShellScriptBin "librenms-artisan" ''
+    cd ${package}
+    sudo=exec
+    if [[ "$USER" != ${cfg.user} ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}'
+    fi
+    $sudo ${package}/artisan $*
+  '';
+
+  lnmsWrapper = pkgs.writeShellScriptBin "lnms" ''
+    cd ${package}
+    exec ${package}/lnms $*
+  '';
+
+  configFile = pkgs.writeText "config.php" ''
+    <?php
+    $new_config = json_decode(file_get_contents("${cfg.dataDir}/config.json"), true);
+    $config = ($config == null) ? $new_config : array_merge($config, $new_config);
+
+    ${lib.optionalString (cfg.extraConfig != null) cfg.extraConfig}
+  '';
+
+in {
+  options.services.librenms = with lib; {
+    enable = mkEnableOption "LibreNMS network monitoring system";
+
+    user = mkOption {
+      type = types.str;
+      default = "librenms";
+      description = ''
+        Name of the LibreNMS user.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "librenms";
+      description = ''
+        Name of the LibreNMS group.
+      '';
+    };
+
+    hostname = mkOption {
+      type = types.str;
+      default = config.networking.fqdnOrHostName;
+      defaultText = literalExpression "config.networking.fqdnOrHostName";
+      description = ''
+        The hostname to serve LibreNMS on.
+      '';
+    };
+
+    pollerThreads = mkOption {
+      type = types.int;
+      default = 16;
+      description = ''
+        Amount of threads of the cron-poller.
+      '';
+    };
+
+    enableOneMinutePolling = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enables the [1-Minute Polling](https://docs.librenms.org/Support/1-Minute-Polling/).
+        Changing this option will automatically convert your existing rrd files.
+      '';
+    };
+
+    useDistributedPollers = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enables (distributed pollers)[https://docs.librenms.org/Extensions/Distributed-Poller/]
+        for this LibreNMS instance. This will enable a local `rrdcached` and `memcached` server.
+
+        To use this feature, make sure to configure your firewall that the distributed pollers
+        can reach the local `mysql`, `rrdcached` and `memcached` ports.
+      '';
+    };
+
+    distributedPoller = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Configure this LibreNMS instance as a (distributed poller)[https://docs.librenms.org/Extensions/Distributed-Poller/].
+          This will disable all web features and just configure the poller features.
+          Use the `mysql` database of your main LibreNMS instance in the database settings.
+        '';
+      };
+
+      name = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Custom name of this poller.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "0";
+        example = "1,2";
+        description = ''
+          Group(s) of this poller.
+        '';
+      };
+
+      distributedBilling = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable distributed billing on this poller.
+        '';
+      };
+
+      memcachedHost = mkOption {
+        type = types.str;
+        description = ''
+          Hostname or IP of the `memcached` server.
+        '';
+      };
+
+      memcachedPort = mkOption {
+        type = types.port;
+        default = 11211;
+        description = ''
+          Port of the `memcached` server.
+        '';
+      };
+
+      rrdcachedHost = mkOption {
+        type = types.str;
+        description = ''
+          Hostname or IP of the `rrdcached` server.
+        '';
+      };
+
+      rrdcachedPort = mkOption {
+        type = types.port;
+        default = 42217;
+        description = ''
+          Port of the `memcached` server.
+        '';
+      };
+    };
+
+    poolConfig = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = ''
+        Options for the LibreNMS PHP pool. See the documentation on `php-fpm.conf`
+        for details on configuration directives.
+      '';
+    };
+
+    nginx = mkOption {
+      type = types.submodule (
+        recursiveUpdate
+          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
+      );
+      default = { };
+      example = literalExpression ''
+        {
+          serverAliases = [
+            "librenms.''${config.networking.domain}"
+          ];
+          # To enable encryption and let let's encrypt take care of certificate
+          forceSSL = true;
+          enableACME = true;
+          # To set the LibreNMS virtualHost as the default virtualHost;
+          default = true;
+        }
+      '';
+      description = ''
+        With this option, you can customize the nginx virtualHost settings.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/librenms";
+      description = ''
+        Path of the LibreNMS state directory.
+      '';
+    };
+
+    logDir = mkOption {
+      type = types.path;
+      default = "/var/log/librenms";
+      description = ''
+        Path of the LibreNMS logging directory.
+      '';
+    };
+
+    database = {
+      createLocally = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to create a local database automatically.
+        '';
+      };
+
+      host = mkOption {
+        default = "localhost";
+        description = ''
+          Hostname or IP of the MySQL/MariaDB server.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 3306;
+        description = ''
+          Port of the MySQL/MariaDB server.
+        '';
+      };
+
+      database = mkOption {
+        type = types.str;
+        default = "librenms";
+        description = ''
+          Name of the database on the MySQL/MariaDB server.
+        '';
+      };
+
+      username = mkOption {
+        type = types.str;
+        default = "librenms";
+        description = ''
+          Name of the user on the MySQL/MariaDB server.
+        '';
+      };
+
+      passwordFile = mkOption {
+        type = types.path;
+        example = "/run/secrets/mysql.pass";
+        description = ''
+          A file containing the password for the user of the MySQL/MariaDB server.
+          Must be readable for the LibreNMS user.
+        '';
+      };
+    };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        File containing env-vars to be substituted into the final config. Useful for secrets.
+        Does not apply to settings defined in `extraConfig`.
+      '';
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {};
+      };
+      description = ''
+        Attrset of the LibreNMS configuration.
+        See https://docs.librenms.org/Support/Configuration/ for reference.
+        All possible options are listed [here](https://github.com/librenms/librenms/blob/master/misc/config_definitions.json).
+        See https://docs.librenms.org/Extensions/Authentication/ for setting other authentication methods.
+      '';
+      default = { };
+      example = {
+        base_url = "/librenms/";
+        top_devices = true;
+        top_ports = false;
+      };
+    };
+
+    extraConfig = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Additional config for LibreNMS that will be appended to the `config.php`. See
+        https://github.com/librenms/librenms/blob/master/misc/config_definitions.json
+        for possible options. Useful if you want to use PHP-Functions in your config.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = config.time.timeZone != null;
+        message = "You must set `time.timeZone` to use the LibreNMS module.";
+      }
+      {
+        assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
+        message = "The database host must be \"localhost\" if services.librenms.database.createLocally is set to true.";
+      }
+      {
+        assertion = !(cfg.useDistributedPollers && cfg.distributedPoller.enable);
+        message = "The LibreNMS instance can't be a distributed poller and a full instance at the same time.";
+      }
+    ];
+
+    users.users.${cfg.user} = {
+      group = "${cfg.group}";
+      isSystemUser = true;
+    };
+
+    users.groups.${cfg.group} = { };
+
+    services.librenms.settings = {
+      # basic configs
+      "user" = cfg.user;
+      "own_hostname" = cfg.hostname;
+      "base_url" = lib.mkDefault "/";
+      "auth_mechanism" = lib.mkDefault "mysql";
+
+      # disable auto update function (won't work with NixOS)
+      "update" = false;
+
+      # enable fast ping by default
+      "ping_rrd_step" = 60;
+
+      # one minute polling
+      "rrd.step" = if cfg.enableOneMinutePolling then 60 else 300;
+      "rrd.heartbeat" = if cfg.enableOneMinutePolling then 120 else 600;
+    } // (lib.optionalAttrs cfg.distributedPoller.enable {
+      "distributed_poller" = true;
+      "distributed_poller_name" = lib.mkIf (cfg.distributedPoller.name != null) cfg.distributedPoller.name;
+      "distributed_poller_group" = cfg.distributedPoller.group;
+      "distributed_billing" = cfg.distributedPoller.distributedBilling;
+      "distributed_poller_memcached_host" = cfg.distributedPoller.memcachedHost;
+      "distributed_poller_memcached_port" = cfg.distributedPoller.memcachedPort;
+      "rrdcached" = "${cfg.distributedPoller.rrdcachedHost}:${toString cfg.distributedPoller.rrdcachedPort}";
+    }) // (lib.optionalAttrs cfg.useDistributedPollers {
+      "distributed_poller" = true;
+      # still enable a local poller with distributed polling
+      "distributed_poller_group" = lib.mkDefault "0";
+      "distributed_billing" = lib.mkDefault true;
+      "distributed_poller_memcached_host" = "localhost";
+      "distributed_poller_memcached_port" = 11211;
+      "rrdcached" = "localhost:42217";
+    });
+
+    services.memcached = lib.mkIf cfg.useDistributedPollers {
+      enable = true;
+      listen = "0.0.0.0";
+    };
+
+    systemd.services.rrdcached = lib.mkIf cfg.useDistributedPollers {
+      description = "rrdcached";
+      after = [ "librenms-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "forking";
+        User = cfg.user;
+        Group = cfg.group;
+        LimitNOFILE = 16384;
+        RuntimeDirectory = "rrdcached";
+        PidFile = "/run/rrdcached/rrdcached.pid";
+        # rrdcached params from https://docs.librenms.org/Extensions/Distributed-Poller/#config-sample
+        ExecStart = "${pkgs.rrdtool}/bin/rrdcached -l 0:42217 -R -j ${cfg.dataDir}/rrdcached-journal/ -F -b ${cfg.dataDir}/rrd -B -w 1800 -z 900 -p /run/rrdcached/rrdcached.pid";
+      };
+    };
+
+    services.mysql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      package = lib.mkDefault pkgs.mariadb;
+      settings.mysqld = {
+        innodb_file_per_table = 1;
+        lower_case_table_names = 0;
+      } // (lib.optionalAttrs cfg.useDistributedPollers {
+        bind-address = "0.0.0.0";
+      });
+      ensureDatabases = [ cfg.database.database ];
+      ensureUsers = [
+        {
+          name = cfg.database.username;
+          ensurePermissions = {
+            "${cfg.database.database}.*" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+      initialScript = lib.mkIf cfg.useDistributedPollers (pkgs.writeText "mysql-librenms-init" ''
+        CREATE USER IF NOT EXISTS '${cfg.database.username}'@'%';
+        GRANT ALL PRIVILEGES ON ${cfg.database.database}.* TO '${cfg.database.username}'@'%';
+      '');
+    };
+
+    services.nginx = lib.mkIf (!cfg.distributedPoller.enable) {
+      enable = true;
+      virtualHosts."${cfg.hostname}" = lib.mkMerge [
+        cfg.nginx
+        {
+          root = lib.mkForce "${package}/html";
+          locations."/" = {
+            index = "index.php";
+            tryFiles = "$uri $uri/ /index.php?$query_string";
+          };
+          locations."~ .php$".extraConfig = ''
+            fastcgi_pass unix:${config.services.phpfpm.pools."librenms".socket};
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+          '';
+        }
+      ];
+    };
+
+    services.phpfpm.pools.librenms = lib.mkIf (!cfg.distributedPoller.enable) {
+      user = cfg.user;
+      group = cfg.group;
+      inherit (package) phpPackage;
+      inherit phpOptions;
+      settings = {
+        "listen.mode" = "0660";
+        "listen.owner" = config.services.nginx.user;
+        "listen.group" = config.services.nginx.group;
+      } // cfg.poolConfig;
+    };
+
+    systemd.services.librenms-scheduler = {
+      description = "LibreNMS Scheduler";
+      path = [ pkgs.unixtools.whereis ];
+      serviceConfig = {
+        Type = "oneshot";
+        WorkingDirectory = package;
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${artisanWrapper}/bin/librenms-artisan schedule:run";
+      };
+    };
+
+    systemd.timers.librenms-scheduler = {
+      description = "LibreNMS Scheduler";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "minutely";
+        AccuracySec = "1second";
+      };
+    };
+
+    systemd.services.librenms-setup = {
+      description = "Preparation tasks for LibreNMS";
+      before = [ "phpfpm-librenms.service" ];
+      after = [ "systemd-tmpfiles-setup.service" ]
+        ++ (lib.optional (cfg.database.host == "localhost") "mysql.service");
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ package configFile ];
+      path = [ pkgs.mariadb pkgs.unixtools.whereis pkgs.gnused ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStartPre = lib.mkIf cfg.database.createLocally [ "!${pkgs.writeShellScript "librenms-db-init" ''
+          DB_PASSWORD=$(cat ${cfg.database.passwordFile} | tr -d '\n')
+          echo "ALTER USER '${cfg.database.username}'@'localhost' IDENTIFIED BY '$DB_PASSWORD';" | ${pkgs.mariadb}/bin/mysql
+          ${lib.optionalString cfg.useDistributedPollers ''
+            echo "ALTER USER '${cfg.database.username}'@'%' IDENTIFIED BY '$DB_PASSWORD';" | ${pkgs.mariadb}/bin/mysql
+          ''}
+        ''}"];
+      };
+      script = ''
+        set -euo pipefail
+
+        # config setup
+        ln -sf ${configFile} ${cfg.dataDir}/config.php
+        ${pkgs.envsubst}/bin/envsubst -i ${configJson} -o ${cfg.dataDir}/config.json
+        export PHPRC=${phpIni}
+
+        if [[ ! -s ${cfg.dataDir}/.env ]]; then
+          # init .env file
+          echo "APP_KEY=" > ${cfg.dataDir}/.env
+          ${artisanWrapper}/bin/librenms-artisan key:generate --ansi
+          ${artisanWrapper}/bin/librenms-artisan webpush:vapid
+          echo "" >> ${cfg.dataDir}/.env
+          echo -n "NODE_ID=" >> ${cfg.dataDir}/.env
+          ${package.phpPackage}/bin/php -r "echo uniqid();" >> ${cfg.dataDir}/.env
+          echo "" >> ${cfg.dataDir}/.env
+        else
+          # .env file already exists --> only update database and cache config
+          ${pkgs.gnused}/bin/sed -i /^DB_/d ${cfg.dataDir}/.env
+          ${pkgs.gnused}/bin/sed -i /^CACHE_DRIVER/d ${cfg.dataDir}/.env
+        fi
+        ${lib.optionalString (cfg.useDistributedPollers || cfg.distributedPoller.enable) ''
+          echo "CACHE_DRIVER=memcached" >> ${cfg.dataDir}/.env
+        ''}
+        echo "DB_HOST=${cfg.database.host}" >> ${cfg.dataDir}/.env
+        echo "DB_PORT=${toString cfg.database.port}" >> ${cfg.dataDir}/.env
+        echo "DB_DATABASE=${cfg.database.database}" >> ${cfg.dataDir}/.env
+        echo "DB_USERNAME=${cfg.database.username}" >> ${cfg.dataDir}/.env
+        echo -n "DB_PASSWORD=" >> ${cfg.dataDir}/.env
+        cat ${cfg.database.passwordFile} >> ${cfg.dataDir}/.env
+
+        # clear cache after update
+        OLD_VERSION=$(cat ${cfg.dataDir}/version)
+        if [[ $OLD_VERSION != "${package.version}" ]]; then
+          rm -r ${cfg.dataDir}/cache/*
+          echo "${package.version}" > ${cfg.dataDir}/version
+        fi
+
+        # convert rrd files when the oneMinutePolling option is changed
+        OLD_ENABLED=$(cat ${cfg.dataDir}/one_minute_enabled)
+        if [[ $OLD_ENABLED != "${lib.boolToString cfg.enableOneMinutePolling}" ]]; then
+          ${package}/scripts/rrdstep.php -h all
+          echo "${lib.boolToString cfg.enableOneMinutePolling}" > ${cfg.dataDir}/one_minute_enabled
+        fi
+
+        # migrate db
+        ${artisanWrapper}/bin/librenms-artisan migrate --force --no-interaction
+      '';
+    };
+
+    programs.mtr.enable = true;
+
+    services.logrotate = {
+      enable = true;
+      settings."${cfg.logDir}/librenms.log" = {
+        su = "${cfg.user} ${cfg.group}";
+        create = "0640 ${cfg.user} ${cfg.group}";
+        rotate = 6;
+        frequency = "weekly";
+        compress = true;
+        delaycompress = true;
+        missingok = true;
+        notifempty = true;
+      };
+    };
+
+    services.cron = {
+      enable = true;
+      systemCronJobs = let
+        env = "PHPRC=${phpIni}";
+      in [
+        # based on crontab provided by LibreNMS
+        "33 */6 * * * ${cfg.user} ${env} ${package}/cronic ${package}/discovery-wrapper.py 1"
+        "*/5 * * * * ${cfg.user} ${env} ${package}/discovery.php -h new >> /dev/null 2>&1"
+
+        "${if cfg.enableOneMinutePolling then "*" else "*/5"} * * * * ${cfg.user} ${env} ${package}/cronic ${package}/poller-wrapper.py ${toString cfg.pollerThreads}"
+        "* * * * * ${cfg.user} ${env} ${package}/alerts.php >> /dev/null 2>&1"
+
+        "*/5 * * * * ${cfg.user} ${env} ${package}/poll-billing.php >> /dev/null 2>&1"
+        "01 * * * * ${cfg.user} ${env} ${package}/billing-calculate.php >> /dev/null 2>&1"
+        "*/5 * * * * ${cfg.user} ${env} ${package}/check-services.php >> /dev/null 2>&1"
+
+        # extra: fast ping
+        "* * * * * ${cfg.user} ${env} ${package}/ping.php >> /dev/null 2>&1"
+
+        # daily.sh tasks are split to exclude update
+        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh cleanup >> /dev/null 2>&1"
+        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh notifications >> /dev/null 2>&1"
+        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh peeringdb >> /dev/null 2>&1"
+        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh mac_oui >> /dev/null 2>&1"
+      ];
+    };
+
+    security.wrappers = {
+      fping = {
+        setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.fping}/bin/fping";
+      };
+    };
+
+    environment.systemPackages = [ artisanWrapper lnmsWrapper ];
+
+    systemd.tmpfiles.rules = [
+      "d ${cfg.logDir}                               0750 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.logDir}/librenms.log                  0640 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}                              0750 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.dataDir}/.env                         0600 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.dataDir}/version                      0600 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.dataDir}/one_minute_enabled           0600 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.dataDir}/config.json                  0600 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage                      0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/app                  0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/debugbar             0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/framework            0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/framework/cache      0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/framework/sessions   0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/framework/views      0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/logs                 0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/rrd                          0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/cache                        0700 ${cfg.user} ${cfg.group} - -"
+    ] ++ lib.optionals cfg.useDistributedPollers [
+      "d ${cfg.dataDir}/rrdcached-journal            0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+  };
+
+  meta.maintainers = lib.teams.wdz.members;
+}
diff --git a/nixos/modules/services/monitoring/mackerel-agent.nix b/nixos/modules/services/monitoring/mackerel-agent.nix
index 67dc1bc19ed..62a7858500f 100644
--- a/nixos/modules/services/monitoring/mackerel-agent.nix
+++ b/nixos/modules/services/monitoring/mackerel-agent.nix
@@ -11,10 +11,10 @@ in {
 
     # the upstream package runs as root, but doesn't seem to be strictly
     # necessary for basic functionality
-    runAsRoot = mkEnableOption (lib.mdDoc "Whether to run as root");
+    runAsRoot = mkEnableOption (lib.mdDoc "running as root");
 
     autoRetirement = mkEnableOption (lib.mdDoc ''
-      Whether to automatically retire the host upon OS shutdown.
+      retiring the host upon OS shutdown
     '');
 
     apiKeyFile = mkOption {
@@ -59,7 +59,7 @@ in {
         };
 
         options.diagnostic =
-          mkEnableOption (lib.mdDoc "Collect memory usage for the agent itself");
+          mkEnableOption (lib.mdDoc "collecting memory usage for the agent itself");
       };
     };
   };
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index f37f2689927..5ed7cac48ae 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -4,7 +4,7 @@
 # TODO: LWP/Pg perl libs aren't recognized
 
 # TODO: support fastcgi
-# http://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
+# https://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
 # spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
 # spawn-fcgi -s /run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
 # https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
@@ -83,42 +83,47 @@ let
   # Copy one Munin plugin into the Nix store with a specific name.
   # This is suitable for use with plugins going directly into /etc/munin/plugins,
   # i.e. munin.extraPlugins.
-  internOnePlugin = name: path:
+  internOnePlugin = { name, path }:
     "cp -a '${path}' '${name}'";
 
   # Copy an entire tree of Munin plugins into a single directory in the Nix
-  # store, with no renaming.
-  # This is suitable for use with munin-node-configure --suggest, i.e.
-  # munin.extraAutoPlugins.
-  internManyPlugins = name: path:
+  # store, with no renaming. The output is suitable for use with
+  # munin-node-configure --suggest, i.e. munin.extraAutoPlugins.
+  # Note that this flattens the input; this is intentional, as
+  # munin-node-configure won't recurse into subdirectories.
+  internManyPlugins = path:
     "find '${path}' -type f -perm /a+x -exec cp -a -t . '{}' '+'";
 
   # Use the appropriate intern-fn to copy the plugins into the store and patch
   # them afterwards in an attempt to get them to run on NixOS.
+  # This is a bit hairy because we can't just fix shebangs; lots of munin plugins
+  # hardcode paths like /sbin/mount rather than trusting $PATH, so we have to
+  # look for and update those throughout the script. At the same time, if the
+  # plugin comes from a package that is already nixified, we don't want to
+  # rewrite paths like /nix/store/foo/sbin/mount.
+  # For now we make the simplifying assumption that no file will contain lines
+  # which mix store paths and FHS paths, and thus run our substitution only on
+  # lines which do not contain store paths.
   internAndFixPlugins = name: intern-fn: paths:
     pkgs.runCommand name {} ''
       mkdir -p "$out"
       cd "$out"
-      ${lib.concatStringsSep "\n"
-          (lib.attrsets.mapAttrsToList intern-fn paths)}
+      ${lib.concatStringsSep "\n" (map intern-fn paths)}
       chmod -R u+w .
-      find . -type f -exec sed -E -i '
-        s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
-      ' '{}' '+'
+      ${pkgs.findutils}/bin/find . -type f -exec ${pkgs.gnused}/bin/sed -E -i "
+        \%''${NIX_STORE}/%! s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
+      " '{}' '+'
     '';
 
   # TODO: write a derivation for munin-contrib, so that for contrib plugins
   # you can just refer to them by name rather than needing to include a copy
   # of munin-contrib in your nixos configuration.
   extraPluginDir = internAndFixPlugins "munin-extra-plugins.d"
-    internOnePlugin nodeCfg.extraPlugins;
+    internOnePlugin
+    (lib.attrsets.mapAttrsToList (k: v: { name = k; path = v; }) nodeCfg.extraPlugins);
 
   extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d"
-    internManyPlugins
-    (builtins.listToAttrs
-      (map
-        (path: { name = baseNameOf path; value = path; })
-        nodeCfg.extraAutoPlugins));
+    internManyPlugins nodeCfg.extraAutoPlugins;
 
   customStaticDir = pkgs.runCommand "munin-custom-static-data" {} ''
     cp -a "${pkgs.munin}/etc/opt/munin/static" "$out"
@@ -142,7 +147,7 @@ in
           Enable Munin Node agent. Munin node listens on 0.0.0.0 and
           by default accepts connections only from 127.0.0.1 for security reasons.
 
-          See <http://guide.munin-monitoring.org/en/latest/architecture/index.html>.
+          See <https://guide.munin-monitoring.org/en/latest/architecture/index.html>.
         '';
       };
 
@@ -151,7 +156,7 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`munin-node.conf` extra configuration. See
-          <http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
+          <https://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
         '';
       };
 
@@ -160,7 +165,7 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`plugin-conf.d` extra plugin configuration. See
-          <http://guide.munin-monitoring.org/en/latest/plugin/use.html>
+          <https://guide.munin-monitoring.org/en/latest/plugin/use.html>
         '';
         example = ''
           [fail2ban_*]
@@ -268,9 +273,9 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`munin.conf` extra global configuration.
-          See <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
+          See <https://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
           Useful to setup notifications, see
-          <http://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
+          <https://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
         '';
         example = ''
           contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
@@ -283,7 +288,7 @@ in
         description = lib.mdDoc ''
           Definitions of hosts of nodes to collect data from. Needs at least one
           host for cron to succeed. See
-          <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
+          <https://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
         '';
         example = literalExpression ''
           '''
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 8feff22c118..dc5fa1be292 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -88,7 +88,7 @@ in
 
   options = {
     services.nagios = {
-      enable = mkEnableOption (lib.mdDoc ''[Nagios](http://www.nagios.org/) to monitor your system or network.'');
+      enable = mkEnableOption (lib.mdDoc ''[Nagios](https://www.nagios.org/) to monitor your system or network.'');
 
       objectDefs = mkOption {
         description = lib.mdDoc ''
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 3833418b5ad..de0e044453e 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -12,6 +12,7 @@ let
     ln -s /run/wrappers/bin/perf.plugin $out/libexec/netdata/plugins.d/perf.plugin
     ln -s /run/wrappers/bin/slabinfo.plugin $out/libexec/netdata/plugins.d/slabinfo.plugin
     ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin
+    ln -s /run/wrappers/bin/systemd-journal.plugin $out/libexec/netdata/plugins.d/systemd-journal.plugin
   '';
 
   plugins = [
@@ -254,7 +255,7 @@ in {
         # Capabilities
         CapabilityBoundingSet = [
           "CAP_DAC_OVERRIDE"      # is required for freeipmi and slabinfo plugins
-          "CAP_DAC_READ_SEARCH"   # is required for apps plugin
+          "CAP_DAC_READ_SEARCH"   # is required for apps and systemd-journal plugin
           "CAP_FOWNER"            # is required for freeipmi plugin
           "CAP_SETPCAP"           # is required for apps, perf and slabinfo plugins
           "CAP_SYS_ADMIN"         # is required for perf plugin
@@ -263,6 +264,7 @@ in {
           "CAP_NET_RAW"           # is required for fping app
           "CAP_SYS_CHROOT"        # is required for cgroups plugin
           "CAP_SETUID"            # is required for cgroups and cgroups-network plugins
+          "CAP_SYSLOG"            # is required for systemd-journal plugin
         ];
         # Sandboxing
         ProtectSystem = "full";
@@ -318,6 +320,14 @@ in {
         permissions = "u+rx,g+x,o-rwx";
       };
 
+      "systemd-journal.plugin" = {
+        source = "${cfg.package}/libexec/netdata/plugins.d/systemd-journal.plugin.org";
+        capabilities = "cap_dac_read_search,cap_syslog+ep";
+        owner = cfg.user;
+        group = cfg.group;
+        permissions = "u+rx,g+x,o-rwx";
+      };
+
       "slabinfo.plugin" = {
         source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org";
         capabilities = "cap_dac_override+ep";
diff --git a/nixos/modules/services/monitoring/parsedmarc.nix b/nixos/modules/services/monitoring/parsedmarc.nix
index 44fc359b6a7..a146e7ab954 100644
--- a/nixos/modules/services/monitoring/parsedmarc.nix
+++ b/nixos/modules/services/monitoring/parsedmarc.nix
@@ -301,6 +301,7 @@ in
               description = lib.mdDoc ''
                 The addresses to send outgoing mail to.
               '';
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
             };
           };
 
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 987f17c2c6e..5fb543ec619 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -8,7 +8,7 @@ let
 
   checkedConfig = file:
     if cfg.checkConfig then
-      pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
+      pkgs.runCommand "checked-config" { nativeBuildInputs = [ cfg.package ]; } ''
         ln -s ${file} $out
         amtool check-config $out
       '' else file;
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 19ee3ae6f7d..a38855ccd40 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -31,7 +31,7 @@ let
     if checkConfigEnabled then
       pkgs.runCommandLocal
         "${name}-${replaceStrings [" "] [""] what}-checked"
-        { buildInputs = [ cfg.package.cli ]; } ''
+        { nativeBuildInputs = [ cfg.package.cli ]; } ''
         ln -s ${file} $out
         promtool ${what} $out
       '' else file;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 1d06893bf1d..f89522c0986 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -2,8 +2,8 @@
 
 let
   inherit (lib) concatStrings foldl foldl' genAttrs literalExpression maintainers
-                mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
-                optional types mkOptionDefault flip attrNames;
+    mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
+    optional types mkOptionDefault flip attrNames;
 
   cfg = config.services.prometheus.exporters;
 
@@ -20,7 +20,7 @@ let
   #  systemd service must be provided by specifying either
   #  `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
 
-  exporterOpts = genAttrs [
+  exporterOpts = (genAttrs [
     "apcupsd"
     "artifactory"
     "bind"
@@ -34,13 +34,15 @@ let
     "domain"
     "dovecot"
     "fastly"
+    "flow"
     "fritzbox"
     "graphite"
     "idrac"
+    "imap-mailstat"
     "influxdb"
     "ipmi"
-    "json"
     "jitsi"
+    "json"
     "junos-czerwonk"
     "kea"
     "keylight"
@@ -58,6 +60,7 @@ let
     "nut"
     "openldap"
     "openvpn"
+    "pgbouncer"
     "php-fpm"
     "pihole"
     "postfix"
@@ -72,9 +75,9 @@ let
     "scaphandre"
     "script"
     "shelly"
-    "snmp"
     "smartctl"
     "smokeping"
+    "snmp"
     "sql"
     "statsd"
     "surfboard"
@@ -86,10 +89,39 @@ let
     "v2ray"
     "varnish"
     "wireguard"
-    "flow"
     "zfs"
-  ] (name:
-    import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+  ]
+    (name:
+      import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+    )) // (mapAttrs
+    (name: params:
+      import (./. + "/exporters/${params.name}.nix") { inherit config lib pkgs options; type = params.type ; })
+    {
+      exportarr-bazarr = {
+        name = "exportarr";
+        type = "bazarr";
+      };
+      exportarr-lidarr = {
+        name = "exportarr";
+        type = "lidarr";
+      };
+      exportarr-prowlarr = {
+        name = "exportarr";
+        type = "prowlarr";
+      };
+      exportarr-radarr = {
+        name = "exportarr";
+        type = "radarr";
+      };
+      exportarr-readarr = {
+        name = "exportarr";
+        type = "readarr";
+      };
+      exportarr-sonarr = {
+        name = "exportarr";
+        type = "sonarr";
+      };
+    }
   );
 
   mkExporterOpts = ({ name, port }: {
@@ -313,6 +345,25 @@ in
           'services.prometheus.exporters.nextcloud.tokenFile'
       '';
     } {
+      assertion =  cfg.pgbouncer.enable -> (
+        (cfg.pgbouncer.connectionStringFile != null || cfg.pgbouncer.connectionString != "")
+      );
+        message = ''
+          PgBouncer exporter needs either connectionStringFile or connectionString configured"
+        '';
+    } {
+      assertion = cfg.pgbouncer.enable -> (
+        config.services.pgbouncer.ignoreStartupParameters != null && builtins.match ".*extra_float_digits.*" config.services.pgbouncer.ignoreStartupParameters != null
+        );
+        message = ''
+          Prometheus PgBouncer exporter requires including `extra_float_digits` in services.pgbouncer.ignoreStartupParameters
+
+          Example:
+          services.pgbouncer.ignoreStartupParameters = extra_float_digits;
+
+          See https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration
+        '';
+    } {
       assertion = cfg.sql.enable -> (
         (cfg.sql.configFile == null) != (cfg.sql.configuration == null)
       );
@@ -350,12 +401,24 @@ in
         `openFirewall' is set to `true'!
       '';
     })) ++ config.services.prometheus.exporters.assertions;
-    warnings = [(mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) ''
-        Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override
-        `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`.
-        Consider using `services.prometheus.exporters.idrac.configuration` instead.
-      ''
-    )] ++ config.services.prometheus.exporters.warnings;
+    warnings = [
+      (mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) ''
+          Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override
+          `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`.
+          Consider using `services.prometheus.exporters.idrac.configuration` instead.
+        ''
+      )
+      (mkIf
+        (cfg.pgbouncer.enable && cfg.pgbouncer.connectionString != "") ''
+          config.services.prometheus.exporters.pgbouncer.connectionString is insecure. Use connectionStringFile instead.
+        ''
+      )
+      (mkIf
+        (cfg.pgbouncer.enable && config.services.pgbouncer.authType != "any") ''
+          Admin user (with password or passwordless) MUST exist in the services.pgbouncer.authFile if authType other than any is used.
+        ''
+      )
+    ] ++ config.services.prometheus.exporters.warnings;
   }] ++ [(mkIf config.services.minio.enable {
     services.prometheus.exporters.minio.minioAddress  = mkDefault "http://localhost:9000";
     services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
index 407bff1d62d..ce2c391de52 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -25,7 +25,7 @@ let
   checkConfig = file:
     pkgs.runCommand "checked-blackbox-exporter.conf" {
       preferLocalBuild = true;
-      buildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ];
+      nativeBuildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ];
     } ''
       ln -s ${coerceConfigFile file} $out
       blackbox_exporter --config.check --config.file $out
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix b/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix
new file mode 100644
index 00000000000..13220933541
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, options, type }:
+
+let
+  cfg = config.services.prometheus.exporters."exportarr-${type}";
+  exportarrEnvironment = (
+    lib.mapAttrs (_: toString) cfg.environment
+  ) // {
+    PORT = toString cfg.port;
+    URL = cfg.url;
+    API_KEY_FILE = lib.mkIf (cfg.apiKeyFile != null) "%d/api-key";
+  };
+in
+{
+  port = 9708;
+  extraOpts = {
+    url = lib.mkOption {
+      type = lib.types.str;
+      default = "http://127.0.0.1";
+      description = lib.mdDoc ''
+        The full URL to Sonarr, Radarr, or Lidarr.
+      '';
+    };
+
+    apiKeyFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = lib.mdDoc ''
+        File containing the api-key.
+      '';
+    };
+
+    package = lib.mkPackageOptionMD pkgs "exportarr" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      default = { };
+      description = lib.mdDoc ''
+        See [the configuration guide](https://github.com/onedr0p/exportarr#configuration) for available options.
+      '';
+      example = {
+        PROWLARR__BACKFILL = true;
+      };
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      LoadCredential = lib.optionalString (cfg.apiKeyFile != null) "api-key:${cfg.apiKeyFile}";
+      ExecStart = ''${cfg.package}/bin/exportarr ${type} "$@"'';
+      ProcSubset = "pid";
+      ProtectProc = "invisible";
+      SystemCallFilter = ["@system-service" "~@privileged"];
+    };
+    environment = exportarrEnvironment;
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/imap-mailstat.nix b/nixos/modules/services/monitoring/prometheus/exporters/imap-mailstat.nix
new file mode 100644
index 00000000000..c5024a258e7
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/imap-mailstat.nix
@@ -0,0 +1,71 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.imap-mailstat;
+  valueToString = value:
+    if (builtins.typeOf value == "string") then "\"${value}\""
+    else (
+      if (builtins.typeOf value == "int") then "${toString value}"
+      else (
+        if (builtins.typeOf value == "bool") then (if value then "true" else "false")
+        else "XXX ${toString value}"
+      )
+    );
+  createConfigFile = accounts:
+    # unfortunately on toTOML yet
+    # https://github.com/NixOS/nix/issues/3929
+    pkgs.writeText "imap-mailstat-exporter.conf" ''
+      ${concatStrings (attrValues (mapAttrs (name: config: "[[Accounts]]\nname = \"${name}\"\n${concatStrings (attrValues (mapAttrs (k: v: "${k} = ${valueToString v}\n") config))}") accounts))}
+    '';
+  mkOpt = type: description: mkOption {
+    type = types.nullOr type;
+    default = null;
+    description = lib.mdDoc description;
+  };
+  accountOptions.options = {
+    mailaddress = mkOpt types.str "Your email address (at the moment used as login name)";
+    username = mkOpt types.str "If empty string mailaddress value is used";
+    password = mkOpt types.str "";
+    serveraddress = mkOpt types.str "mailserver name or address";
+    serverport = mkOpt types.int "imap port number (at the moment only tls connection is supported)";
+    starttls = mkOpt types.bool "set to true for using STARTTLS to start a TLS connection";
+  };
+in
+{
+  port = 8081;
+  extraOpts = {
+    oldestUnseenDate = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable metric with timestamp of oldest unseen mail
+      '';
+    };
+    accounts = mkOption {
+      type = types.attrsOf (types.submodule accountOptions);
+      default = {};
+      description = lib.mdDoc ''
+        Accounts to monitor
+      '';
+    };
+    configurationFile = mkOption {
+      type = types.path;
+      example = "/path/to/config-file";
+      description = lib.mdDoc ''
+        File containing the configuration
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-imap-mailstat-exporter}/bin/imap-mailstat-exporter \
+          -config ${createConfigFile cfg.accounts} \
+          ${optionalString cfg.oldestUnseenDate "-oldestunseendate"} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
index ed33c72f644..8b1cd47d0a4 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
@@ -15,8 +15,8 @@ in {
       type = types.listOf types.str;
       example = literalExpression ''
         [
-          "/run/kea/kea-dhcp4.socket"
-          "/run/kea/kea-dhcp6.socket"
+          "/run/kea-dhcp4/kea-dhcp4.socket"
+          "/run/kea-dhcp6/kea-dhcp6.socket"
         ]
       '';
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/knot.nix b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
index a73425b37da..77584875080 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
@@ -8,9 +8,9 @@ in {
   port = 9433;
   extraOpts = {
     knotLibraryPath = mkOption {
-      type = types.str;
-      default = "${pkgs.knot-dns.out}/lib/libknot.so";
-      defaultText = literalExpression ''"''${pkgs.knot-dns.out}/lib/libknot.so"'';
+      type = types.nullOr types.str;
+      default = null;
+      example = literalExpression ''"''${pkgs.knot-dns.out}/lib/libknot.so"'';
       description = lib.mdDoc ''
         Path to the library of `knot-dns`.
       '';
@@ -25,7 +25,7 @@ in {
     };
 
     knotSocketTimeout = mkOption {
-      type = types.int;
+      type = types.ints.positive;
       default = 2000;
       description = lib.mdDoc ''
         Timeout in seconds.
@@ -33,17 +33,22 @@ in {
     };
   };
   serviceOpts = {
+    path = with pkgs; [
+      procps
+    ];
     serviceConfig = {
       ExecStart = ''
-        ${pkgs.prometheus-knot-exporter}/bin/knot_exporter \
+        ${pkgs.prometheus-knot-exporter}/bin/knot-exporter \
           --web-listen-addr ${cfg.listenAddress} \
           --web-listen-port ${toString cfg.port} \
-          --knot-library-path ${cfg.knotLibraryPath} \
           --knot-socket-path ${cfg.knotSocketPath} \
           --knot-socket-timeout ${toString cfg.knotSocketTimeout} \
+          ${lib.optionalString (cfg.knotLibraryPath != null) "--knot-library-path ${cfg.knotLibraryPath}"} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
-      SupplementaryGroups = [ "knot" ];
+      SupplementaryGroups = [
+        "knot"
+      ];
       RestrictAddressFamilies = [
         # Need AF_UNIX to collect data
         "AF_UNIX"
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix b/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix
new file mode 100644
index 00000000000..9e55cadae52
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix
@@ -0,0 +1,145 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.pgbouncer;
+in
+{
+  port = 9127;
+  extraOpts = {
+
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = lib.mdDoc ''
+        Path under which to expose metrics.
+      '';
+    };
+
+    connectionString = mkOption {
+      type = types.str;
+      default = "";
+      example = "postgres://admin:@localhost:6432/pgbouncer?sslmode=require";
+      description = lib.mdDoc ''
+        Connection string for accessing pgBouncer.
+
+        NOTE: You MUST keep pgbouncer as database name (special internal db)!!!
+
+        NOTE: Admin user (with password or passwordless) MUST exist
+        in the services.pgbouncer.authFile if authType other than any is used.
+
+        WARNING: this secret is stored in the world-readable Nix store!
+        Use {option}`connectionStringFile` instead.
+      '';
+    };
+
+    connectionStringFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/run/keys/pgBouncer-connection-string";
+      description = lib.mdDoc ''
+        File that contains pgBouncer connection string in format:
+        postgres://admin:@localhost:6432/pgbouncer?sslmode=require
+
+        NOTE: You MUST keep pgbouncer as database name (special internal db)!!!
+
+        NOTE: Admin user (with password or passwordless) MUST exist
+        in the services.pgbouncer.authFile if authType other than any is used.
+
+        {option}`connectionStringFile` takes precedence over {option}`connectionString`
+      '';
+    };
+
+    pidFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Path to PgBouncer pid file.
+
+        If provided, the standard process metrics get exported for the PgBouncer
+        process, prefixed with 'pgbouncer_process_...'. The pgbouncer_process exporter
+        needs to have read access to files owned by the PgBouncer process. Depends on
+        the availability of /proc.
+
+        https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics.
+
+      '';
+    };
+
+    webSystemdSocket = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Use systemd socket activation listeners instead of port listeners (Linux only).
+      '';
+    };
+
+    logLevel = mkOption {
+      type = types.enum ["debug" "info" "warn" "error" ];
+      default = "info";
+      description = lib.mdDoc ''
+        Only log messages with the given severity or above.
+      '';
+    };
+
+    logFormat = mkOption {
+      type = types.enum ["logfmt" "json"];
+      default = "logfmt";
+      description = lib.mdDoc ''
+        Output format of log messages. One of: [logfmt, json]
+      '';
+    };
+
+    webConfigFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Path to configuration file that can enable TLS or authentication.
+      '';
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      description = lib.mdDoc ''
+        Extra commandline options when launching Prometheus.
+      '';
+    };
+
+  };
+
+  serviceOpts = {
+    after = [ "pgbouncer.service" ];
+      serviceConfig = let
+      startScript = pkgs.writeShellScriptBin "pgbouncer-start" "${concatStringsSep " " ([
+            "${pkgs.prometheus-pgbouncer-exporter}/bin/pgbouncer_exporter"
+            "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
+            "--pgBouncer.connectionString ${if cfg.connectionStringFile != null then
+            "$(head -n1 ${cfg.connectionStringFile})" else "${escapeShellArg cfg.connectionString}"}"
+          ]
+            ++ optionals (cfg.telemetryPath != null) [
+            "--web.telemetry-path ${escapeShellArg cfg.telemetryPath}"
+          ]
+            ++ optionals (cfg.pidFile != null) [
+            "--pgBouncer.pid-file= ${escapeShellArg cfg.pidFile}"
+          ]
+            ++ optionals (cfg.logLevel != null) [
+            "--log.level ${escapeShellArg cfg.logLevel}"
+          ]
+            ++ optionals (cfg.logFormat != null) [
+            "--log.format ${escapeShellArg cfg.logFormat}"
+          ]
+            ++ optionals (cfg.webSystemdSocket != false) [
+            "--web.systemd-socket ${escapeShellArg cfg.webSystemdSocket}"
+          ]
+            ++ optionals (cfg.webConfigFile != null) [
+            "--web.config.file ${escapeShellArg cfg.webConfigFile}"
+          ]
+            ++ cfg.extraFlags)}";
+      in
+      {
+        ExecStart = "${startScript}/bin/pgbouncer-start";
+      };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
index c98dcd9f64b..9b759031493 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -11,7 +11,7 @@ in {
     ({ options.warnings = options.warnings; options.assertions = options.assertions; })
   ];
   extraOpts = {
-    verbose = mkEnableOption (lib.mdDoc "Verbose logging mode for prometheus-wireguard-exporter");
+    verbose = mkEnableOption (lib.mdDoc "verbose logging mode for prometheus-wireguard-exporter");
 
     wireguardConfig = mkOption {
       type = with types; nullOr (either path str);
diff --git a/nixos/modules/services/monitoring/smartd.nix b/nixos/modules/services/monitoring/smartd.nix
index 1e654cad5dd..8b79ac0e0c1 100644
--- a/nixos/modules/services/monitoring/smartd.nix
+++ b/nixos/modules/services/monitoring/smartd.nix
@@ -19,7 +19,7 @@ let
       {
       ${pkgs.coreutils}/bin/cat << EOF
       From: smartd on ${host} <${nm.sender}>
-      To: undisclosed-recipients:;
+      To: ${nm.recipient}
       Subject: $SMARTD_SUBJECT
 
       $SMARTD_FULLMESSAGE
diff --git a/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix
index bb11b6a1c1d..efef2d777ac 100644
--- a/nixos/modules/services/monitoring/ups.nix
+++ b/nixos/modules/services/monitoring/ups.nix
@@ -239,11 +239,9 @@ in
 
     power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample";
 
-    system.activationScripts.upsSetup = stringAfter [ "users" "groups" ]
-      ''
-        # Used to store pid files of drivers.
-        mkdir -p /var/state/ups
-      '';
+    systemd.tmpfiles.rules = [
+      "d /var/state/ups -"
+    ];
 
 
 /*
diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix
index 85da416ba6c..503e81b48a5 100644
--- a/nixos/modules/services/monitoring/zabbix-proxy.nix
+++ b/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -203,7 +203,7 @@ in
       { assertion = !config.services.zabbixServer.enable;
         message = "Please choose one of services.zabbixServer or services.zabbixProxy.";
       }
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.zabbixProxy.database.user must be set to ${user} if services.zabbixProxy.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -252,7 +252,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix
index 2b50280e396..0607188d213 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -191,7 +191,7 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
         message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -240,7 +240,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix
index 5a355f3441d..bc746bed31f 100644
--- a/nixos/modules/services/network-filesystems/kubo.nix
+++ b/nixos/modules/services/network-filesystems/kubo.nix
@@ -203,10 +203,8 @@ in
               default = [
                 "/ip4/0.0.0.0/tcp/4001"
                 "/ip6/::/tcp/4001"
-                "/ip4/0.0.0.0/udp/4001/quic"
                 "/ip4/0.0.0.0/udp/4001/quic-v1"
                 "/ip4/0.0.0.0/udp/4001/quic-v1/webtransport"
-                "/ip6/::/udp/4001/quic"
                 "/ip6/::/udp/4001/quic-v1"
                 "/ip6/::/udp/4001/quic-v1/webtransport"
               ];
diff --git a/nixos/modules/services/network-filesystems/openafs/server.nix b/nixos/modules/services/network-filesystems/openafs/server.nix
index ad0fd783567..fbaa7cfc192 100644
--- a/nixos/modules/services/network-filesystems/openafs/server.nix
+++ b/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -177,13 +177,13 @@ in {
 
         backup = {
           enable = mkEnableOption (lib.mdDoc ''
-            Backup server role. When using OpenAFS built-in buserver, use in conjunction with the
+            the backup server role. When using OpenAFS built-in buserver, use in conjunction with the
             `database` role to maintain the Backup
             Database. Normally only used in conjunction with tape storage
             or IBM's Tivoli Storage Manager.
 
             For a modern backup server, enable this role and see
-            {option}`enableFabs`.
+            {option}`enableFabs`
           '');
 
           enableFabs = mkEnableOption (lib.mdDoc ''
diff --git a/nixos/modules/services/network-filesystems/orangefs/server.nix b/nixos/modules/services/network-filesystems/orangefs/server.nix
index e20e7975eba..085b64e4c04 100644
--- a/nixos/modules/services/network-filesystems/orangefs/server.nix
+++ b/nixos/modules/services/network-filesystems/orangefs/server.nix
@@ -192,7 +192,7 @@ in {
     # orangefs daemon will run as user
     users.users.orangefs = {
       isSystemUser = true;
-      group = "orangfs";
+      group = "orangefs";
     };
     users.groups.orangefs = {};
 
diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix
index 1310a374abd..0b22302c0b6 100644
--- a/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixos/modules/services/network-filesystems/samba.nix
@@ -39,7 +39,7 @@ let
   daemonService = appName: args:
     { description = "Samba Service Daemon ${appName}";
 
-      after = [ (mkIf (cfg.enableNmbd && "${appName}" == "smbd") "samba-nmbd.service") ];
+      after = [ (mkIf (cfg.enableNmbd && "${appName}" == "smbd") "samba-nmbd.service") "network.target" ];
       requiredBy = [ "samba.target" ];
       partOf = [ "samba.target" ];
 
diff --git a/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixos/modules/services/network-filesystems/xtreemfs.nix
index 926c3c3bd52..866661cf4e6 100644
--- a/nixos/modules/services/network-filesystems/xtreemfs.nix
+++ b/nixos/modules/services/network-filesystems/xtreemfs.nix
@@ -176,7 +176,7 @@ in
           description = lib.mdDoc ''
             Configuration of XtreemFS DIR service.
             WARNING: configuration is saved as plaintext inside nix store.
-            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
         replication = {
@@ -218,7 +218,7 @@ in
             description = lib.mdDoc ''
               Configuration of XtreemFS DIR replication plugin.
               WARNING: configuration is saved as plaintext inside nix store.
-              For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+              For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
             '';
           };
         };
@@ -319,7 +319,7 @@ in
           description = lib.mdDoc ''
             Configuration of XtreemFS MRC service.
             WARNING: configuration is saved as plaintext inside nix store.
-            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
         replication = {
@@ -361,7 +361,7 @@ in
             description = lib.mdDoc ''
               Configuration of XtreemFS MRC replication plugin.
               WARNING: configuration is saved as plaintext inside nix store.
-              For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+              For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
             '';
           };
         };
@@ -438,7 +438,7 @@ in
           description = lib.mdDoc ''
             Configuration of XtreemFS OSD service.
             WARNING: configuration is saved as plaintext inside nix store.
-            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
       };
diff --git a/nixos/modules/services/networking/asterisk.nix b/nixos/modules/services/networking/asterisk.nix
index 5a1d03f0721..279927781ed 100644
--- a/nixos/modules/services/networking/asterisk.nix
+++ b/nixos/modules/services/networking/asterisk.nix
@@ -139,7 +139,7 @@ in
           path.
 
           See
-          <http://www.asterisk.org/community/documentation>
+          <https://www.asterisk.org/community/documentation/>
           for more examples of what is possible here.
         '';
       };
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
index a86d52b7202..a48066b43b1 100644
--- a/nixos/modules/services/networking/bitcoind.nix
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -3,8 +3,7 @@
 with lib;
 
 let
-
-  eachBitcoind = config.services.bitcoind;
+  eachBitcoind = filterAttrs (bitcoindName: cfg: cfg.enable) config.services.bitcoind;
 
   rpcUserOpts = { name, ... }: {
     options = {
diff --git a/nixos/modules/services/networking/connman.nix b/nixos/modules/services/networking/connman.nix
index 49899141957..c626945ccd0 100644
--- a/nixos/modules/services/networking/connman.nix
+++ b/nixos/modules/services/networking/connman.nix
@@ -1,55 +1,59 @@
 { config, lib, pkgs, ... }:
 
-with pkgs;
-with lib;
-
 let
   cfg = config.services.connman;
   configFile = pkgs.writeText "connman.conf" ''
     [General]
-    NetworkInterfaceBlacklist=${concatStringsSep "," cfg.networkInterfaceBlacklist}
+    NetworkInterfaceBlacklist=${lib.concatStringsSep "," cfg.networkInterfaceBlacklist}
 
     ${cfg.extraConfig}
   '';
   enableIwd = cfg.wifi.backend == "iwd";
 in {
+  meta.maintainers = with lib.maintainers; [ AndersonTorres ];
 
   imports = [
-    (mkRenamedOptionModule [ "networking" "connman" ] [ "services" "connman" ])
+    (lib.mkRenamedOptionModule [ "networking" "connman" ] [ "services" "connman" ])
   ];
 
   ###### interface
 
   options = {
-
     services.connman = {
-
-      enable = mkOption {
-        type = types.bool;
+      enable = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc ''
           Whether to use ConnMan for managing your network connections.
         '';
       };
 
-      enableVPN = mkOption {
-        type = types.bool;
+      package = lib.mkOption {
+        type = lib.types.package;
+        description = lib.mdDoc "The connman package / build flavor";
+        default = pkgs.connman;
+        defaultText = lib.literalExpression "pkgs.connman";
+        example = lib.literalExpression "pkgs.connmanFull";
+      };
+
+      enableVPN = lib.mkOption {
+        type = lib.types.bool;
         default = true;
         description = lib.mdDoc ''
           Whether to enable ConnMan VPN service.
         '';
       };
 
-      extraConfig = mkOption {
-        type = types.lines;
+      extraConfig = lib.mkOption {
+        type = lib.types.lines;
         default = "";
         description = lib.mdDoc ''
           Configuration lines appended to the generated connman configuration file.
         '';
       };
 
-      networkInterfaceBlacklist = mkOption {
-        type = with types; listOf str;
+      networkInterfaceBlacklist = lib.mkOption {
+        type = with lib.types; listOf str;
         default = [ "vmnet" "vboxnet" "virbr" "ifb" "ve" ];
         description = lib.mdDoc ''
           Default blacklisted interfaces, this includes NixOS containers interfaces (ve).
@@ -57,8 +61,8 @@ in {
       };
 
       wifi = {
-        backend = mkOption {
-          type = types.enum [ "wpa_supplicant" "iwd" ];
+        backend = lib.mkOption {
+          type = lib.types.enum [ "wpa_supplicant" "iwd" ];
           default = "wpa_supplicant";
           description = lib.mdDoc ''
             Specify the Wi-Fi backend used.
@@ -67,31 +71,20 @@ in {
         };
       };
 
-      extraFlags = mkOption {
-        type = with types; listOf str;
+      extraFlags = lib.mkOption {
+        type = with lib.types; listOf str;
         default = [ ];
         example = [ "--nodnsproxy" ];
         description = lib.mdDoc ''
           Extra flags to pass to connmand
         '';
       };
-
-      package = mkOption {
-        type = types.package;
-        description = lib.mdDoc "The connman package / build flavor";
-        default = connman;
-        defaultText = literalExpression "pkgs.connman";
-        example = literalExpression "pkgs.connmanFull";
-      };
-
     };
-
   };
 
   ###### implementation
 
-  config = mkIf cfg.enable {
-
+  config = lib.mkIf cfg.enable {
     assertions = [{
       assertion = !config.networking.useDHCP;
       message = "You can not use services.connman with networking.useDHCP";
@@ -107,8 +100,8 @@ in {
     systemd.services.connman = {
       description = "Connection service";
       wantedBy = [ "multi-user.target" ];
-      after = [ "syslog.target" ] ++ optional enableIwd "iwd.service";
-      requires = optional enableIwd "iwd.service";
+      after = [ "syslog.target" ] ++ lib.optional enableIwd "iwd.service";
+      requires = lib.optional enableIwd "iwd.service";
       serviceConfig = {
         Type = "dbus";
         BusName = "net.connman";
@@ -117,13 +110,13 @@ in {
           "${cfg.package}/sbin/connmand"
           "--config=${configFile}"
           "--nodaemon"
-        ] ++ optional enableIwd "--wifi=iwd_agent"
+        ] ++ lib.optional enableIwd "--wifi=iwd_agent"
           ++ cfg.extraFlags);
         StandardOutput = "null";
       };
     };
 
-    systemd.services.connman-vpn = mkIf cfg.enableVPN {
+    systemd.services.connman-vpn = lib.mkIf cfg.enableVPN {
       description = "ConnMan VPN service";
       wantedBy = [ "multi-user.target" ];
       after = [ "syslog.target" ];
@@ -136,7 +129,7 @@ in {
       };
     };
 
-    systemd.services.net-connman-vpn = mkIf cfg.enableVPN {
+    systemd.services.net-connman-vpn = lib.mkIf cfg.enableVPN {
       description = "D-BUS Service";
       serviceConfig = {
         Name = "net.connman.vpn";
@@ -150,9 +143,9 @@ in {
     networking = {
       useDHCP = false;
       wireless = {
-        enable = mkIf (!enableIwd) true;
+        enable = lib.mkIf (!enableIwd) true;
         dbusControlled = true;
-        iwd = mkIf enableIwd {
+        iwd = lib.mkIf enableIwd {
           enable = true;
         };
       };
diff --git a/nixos/modules/services/networking/coredns.nix b/nixos/modules/services/networking/coredns.nix
index f928cdf9614..f1fe7b2f124 100644
--- a/nixos/modules/services/networking/coredns.nix
+++ b/nixos/modules/services/networking/coredns.nix
@@ -29,6 +29,13 @@ in {
       type = types.package;
       description = lib.mdDoc "Coredns package to use.";
     };
+
+    extraArgs = mkOption {
+      default = [];
+      example = [ "-dns.port=53" ];
+      type = types.listOf types.str;
+      description = lib.mdDoc "Extra arguments to pass to coredns.";
+    };
   };
 
   config = mkIf cfg.enable {
@@ -44,7 +51,7 @@ in {
         AmbientCapabilities = "cap_net_bind_service";
         NoNewPrivileges = true;
         DynamicUser = true;
-        ExecStart = "${getBin cfg.package}/bin/coredns -conf=${configFile}";
+        ExecStart = "${getBin cfg.package}/bin/coredns -conf=${configFile} ${lib.escapeShellArgs cfg.extraArgs}";
         ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR1 $MAINPID";
         Restart = "on-failure";
       };
diff --git a/nixos/modules/services/networking/create_ap.nix b/nixos/modules/services/networking/create_ap.nix
index e772cf21ec5..994aa6d36d2 100644
--- a/nixos/modules/services/networking/create_ap.nix
+++ b/nixos/modules/services/networking/create_ap.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.create_ap = {
-      enable = mkEnableOption (lib.mdDoc "setup wifi hotspots using create_ap");
+      enable = mkEnableOption (lib.mdDoc "setting up wifi hotspots using create_ap");
       settings = mkOption {
         type = with types; attrsOf (oneOf [ int bool str ]);
         default = {};
diff --git a/nixos/modules/services/networking/dae.nix b/nixos/modules/services/networking/dae.nix
index 42ed3c7f8d4..cf3fead19be 100644
--- a/nixos/modules/services/networking/dae.nix
+++ b/nixos/modules/services/networking/dae.nix
@@ -14,10 +14,11 @@ in
   options = {
     services.dae = with lib;{
       enable = mkEnableOption
-        (mdDoc "A Linux high-performance transparent proxy solution based on eBPF");
+        (mdDoc "dae, a Linux high-performance transparent proxy solution based on eBPF");
 
       package = mkPackageOptionMD pkgs "dae" { };
 
+
       assets = mkOption {
         type = with types;(listOf path);
         default = with pkgs; [ v2ray-geoip v2ray-domain-list-community ];
@@ -45,9 +46,9 @@ in
       openFirewall = mkOption {
         type = with types; submodule {
           options = {
-            enable = mkEnableOption "enable";
+            enable = mkEnableOption (mdDoc "opening {option}`port` in the firewall");
             port = mkOption {
-              type = types.int;
+              type = types.port;
               description = ''
                 Port to be opened. Consist with field `tproxy_port` in config file.
               '';
@@ -70,8 +71,8 @@ in
       };
 
       configFile = mkOption {
-        type = types.path;
-        default = "/etc/dae/config.dae";
+        type = with types; (nullOr path);
+        default = null;
         example = "/path/to/your/config.dae";
         description = mdDoc ''
           The path of dae config file, end with `.dae`.
@@ -79,12 +80,10 @@ in
       };
 
       config = mkOption {
-        type = types.str;
-        default = ''
-          global{}
-          routing{}
-        '';
+        type = with types; (nullOr str);
+        default = null;
         description = mdDoc ''
+          WARNING: This option will expose store your config unencrypted world-readable in the nix store.
           Config text for dae.
 
           See <https://github.com/daeuniverse/dae/blob/main/example.dae>.
@@ -92,7 +91,7 @@ in
       };
 
       disableTxChecksumIpGeneric =
-        mkEnableOption (mdDoc "See <https://github.com/daeuniverse/dae/issues/43>");
+        mkEnableOption "" // { description = mdDoc "See <https://github.com/daeuniverse/dae/issues/43>"; };
 
     };
   };
@@ -103,11 +102,6 @@ in
       environment.systemPackages = [ cfg.package ];
       systemd.packages = [ cfg.package ];
 
-      environment.etc."dae/config.dae" = {
-        mode = "0400";
-        source = pkgs.writeText "config.dae" cfg.config;
-      };
-
       networking = lib.mkIf cfg.openFirewall.enable {
         firewall =
           let portToOpen = cfg.openFirewall.port;
@@ -121,20 +115,27 @@ in
       systemd.services.dae =
         let
           daeBin = lib.getExe cfg.package;
-          TxChecksumIpGenericWorkaround = with lib;(getExe pkgs.writeShellApplication {
-            name = "disable-tx-checksum-ip-generic";
-            text = with pkgs; ''
-              iface=$(${iproute2}/bin/ip route | ${lib.getExe gawk} '/default/ {print $5}')
-              ${lib.getExe ethtool} -K "$iface" tx-checksum-ip-generic off
-            '';
-          });
+
+          configPath =
+            if cfg.configFile != null
+            then cfg.configFile else pkgs.writeText "config.dae" cfg.config;
+
+          TxChecksumIpGenericWorkaround = with lib;
+            (getExe pkgs.writeShellApplication {
+              name = "disable-tx-checksum-ip-generic";
+              text = with pkgs; ''
+                iface=$(${iproute2}/bin/ip route | ${lib.getExe gawk} '/default/ {print $5}')
+                ${lib.getExe ethtool} -K "$iface" tx-checksum-ip-generic off
+              '';
+            });
         in
         {
           wantedBy = [ "multi-user.target" ];
           serviceConfig = {
-            ExecStartPre = [ "" "${daeBin} validate -c ${cfg.configFile}" ]
+            LoadCredential = [ "config.dae:${configPath}" ];
+            ExecStartPre = [ "" "${daeBin} validate -c \${CREDENTIALS_DIRECTORY}/config.dae" ]
               ++ (with lib; optional cfg.disableTxChecksumIpGeneric TxChecksumIpGenericWorkaround);
-            ExecStart = [ "" "${daeBin} run --disable-timestamp -c ${cfg.configFile}" ];
+            ExecStart = [ "" "${daeBin} run --disable-timestamp -c \${CREDENTIALS_DIRECTORY}/config.dae" ];
             Environment = "DAE_LOCATION_ASSET=${cfg.assetsPath}";
           };
         };
@@ -149,13 +150,21 @@ in
         }
 
         {
-          assertion = !((config.services.dae.config != "global{}\nrouting{}\n")
-            && (config.services.dae.configFile != "/etc/dae/config.dae"));
+          assertion = !((config.services.dae.config != null)
+            && (config.services.dae.configFile != null));
           message = ''
             Option `config` and `configFile` could not be set
             at the same time.
           '';
         }
+
+        {
+          assertion = !((config.services.dae.config == null)
+            && (config.services.dae.configFile == null));
+          message = ''
+            Either `config` or `configFile` should be set.
+          '';
+        }
       ];
     };
 }
diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix
new file mode 100644
index 00000000000..8f4fb0bc78d
--- /dev/null
+++ b/nixos/modules/services/networking/ddclient.nix
@@ -0,0 +1,234 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.ddclient;
+  boolToStr = bool: if bool then "yes" else "no";
+  dataDir = "/var/lib/ddclient";
+  StateDirectory = builtins.baseNameOf dataDir;
+  RuntimeDirectory = StateDirectory;
+
+  configFile' = pkgs.writeText "ddclient.conf" ''
+    # This file can be used as a template for configFile or is automatically generated by Nix options.
+    cache=${dataDir}/ddclient.cache
+    foreground=YES
+    use=${cfg.use}
+    login=${cfg.username}
+    password=${if cfg.protocol == "nsupdate" then "/run/${RuntimeDirectory}/ddclient.key" else "@password_placeholder@"}
+    protocol=${cfg.protocol}
+    ${lib.optionalString (cfg.script != "") "script=${cfg.script}"}
+    ${lib.optionalString (cfg.server != "") "server=${cfg.server}"}
+    ${lib.optionalString (cfg.zone != "")   "zone=${cfg.zone}"}
+    ssl=${boolToStr cfg.ssl}
+    wildcard=YES
+    quiet=${boolToStr cfg.quiet}
+    verbose=${boolToStr cfg.verbose}
+    ${cfg.extraConfig}
+    ${lib.concatStringsSep "," cfg.domains}
+  '';
+  configFile = if (cfg.configFile != null) then cfg.configFile else configFile';
+
+  preStart = ''
+    install --mode=600 --owner=$USER ${configFile} /run/${RuntimeDirectory}/ddclient.conf
+    ${lib.optionalString (cfg.configFile == null) (if (cfg.protocol == "nsupdate") then ''
+      install --mode=600 --owner=$USER ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
+    '' else if (cfg.passwordFile != null) then ''
+      "${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf"
+    '' else ''
+      sed -i '/^password=@password_placeholder@$/d' /run/${RuntimeDirectory}/ddclient.conf
+    '')}
+  '';
+
+in
+
+with lib;
+
+{
+
+  imports = [
+    (mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
+      (config:
+        let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
+        in optional (value != "") value))
+    (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
+    (mkRemovedOptionModule [ "services" "ddclient" "password" ] "Use services.ddclient.passwordFile instead.")
+    (mkRemovedOptionModule [ "services" "ddclient" "ipv6" ] "")
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.ddclient = with lib.types; {
+
+      enable = mkOption {
+        default = false;
+        type = bool;
+        description = lib.mdDoc ''
+          Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
+        '';
+      };
+
+      package = mkOption {
+        type = package;
+        default = pkgs.ddclient;
+        defaultText = lib.literalExpression "pkgs.ddclient";
+        description = lib.mdDoc ''
+          The ddclient executable package run by the service.
+        '';
+      };
+
+      domains = mkOption {
+        default = [ "" ];
+        type = listOf str;
+        description = lib.mdDoc ''
+          Domain name(s) to synchronize.
+        '';
+      };
+
+      username = mkOption {
+        # For `nsupdate` username contains the path to the nsupdate executable
+        default = lib.optionalString (config.services.ddclient.protocol == "nsupdate") "${pkgs.bind.dnsutils}/bin/nsupdate";
+        defaultText = "";
+        type = str;
+        description = lib.mdDoc ''
+          User name.
+        '';
+      };
+
+      passwordFile = mkOption {
+        default = null;
+        type = nullOr str;
+        description = lib.mdDoc ''
+          A file containing the password or a TSIG key in named format when using the nsupdate protocol.
+        '';
+      };
+
+      interval = mkOption {
+        default = "10min";
+        type = str;
+        description = lib.mdDoc ''
+          The interval at which to run the check and update.
+          See {command}`man 7 systemd.time` for the format.
+        '';
+      };
+
+      configFile = mkOption {
+        default = null;
+        type = nullOr path;
+        description = lib.mdDoc ''
+          Path to configuration file.
+          When set this overrides the generated configuration from module options.
+        '';
+        example = "/root/nixos/secrets/ddclient.conf";
+      };
+
+      protocol = mkOption {
+        default = "dyndns2";
+        type = str;
+        description = lib.mdDoc ''
+          Protocol to use with dynamic DNS provider (see https://sourceforge.net/p/ddclient/wiki/protocols).
+        '';
+      };
+
+      server = mkOption {
+        default = "";
+        type = str;
+        description = lib.mdDoc ''
+          Server address.
+        '';
+      };
+
+      ssl = mkOption {
+        default = true;
+        type = bool;
+        description = lib.mdDoc ''
+          Whether to use SSL/TLS to connect to dynamic DNS provider.
+        '';
+      };
+
+      quiet = mkOption {
+        default = false;
+        type = bool;
+        description = lib.mdDoc ''
+          Print no messages for unnecessary updates.
+        '';
+      };
+
+      script = mkOption {
+        default = "";
+        type = str;
+        description = lib.mdDoc ''
+          script as required by some providers.
+        '';
+      };
+
+      use = mkOption {
+        default = "web, web=checkip.dyndns.com/, web-skip='Current IP Address: '";
+        type = str;
+        description = lib.mdDoc ''
+          Method to determine the IP address to send to the dynamic DNS provider.
+        '';
+      };
+
+      verbose = mkOption {
+        default = false;
+        type = bool;
+        description = lib.mdDoc ''
+          Print verbose information.
+        '';
+      };
+
+      zone = mkOption {
+        default = "";
+        type = str;
+        description = lib.mdDoc ''
+          zone as required by some providers.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = lines;
+        description = lib.mdDoc ''
+          Extra configuration. Contents will be added verbatim to the configuration file.
+
+          ::: {.note}
+          `daemon` should not be added here because it does not work great with the systemd-timer approach the service uses.
+          :::
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.ddclient.enable {
+    systemd.services.ddclient = {
+      description = "Dynamic DNS Client";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      restartTriggers = optional (cfg.configFile != null) cfg.configFile;
+      path = lib.optional (lib.hasPrefix "if," cfg.use) pkgs.iproute2;
+
+      serviceConfig = {
+        DynamicUser = true;
+        RuntimeDirectoryMode = "0700";
+        inherit RuntimeDirectory;
+        inherit StateDirectory;
+        Type = "oneshot";
+        ExecStartPre = "!${pkgs.writeShellScript "ddclient-prestart" preStart}";
+        ExecStart = "${lib.getExe cfg.package} -file /run/${RuntimeDirectory}/ddclient.conf";
+      };
+    };
+
+    systemd.timers.ddclient = {
+      description = "Run ddclient";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnBootSec = cfg.interval;
+        OnUnitInactiveSec = cfg.interval;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/deconz.nix b/nixos/modules/services/networking/deconz.nix
new file mode 100644
index 00000000000..05b72470877
--- /dev/null
+++ b/nixos/modules/services/networking/deconz.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.deconz;
+  name = "deconz";
+  stateDir = "/var/lib/${name}";
+  # ref. upstream deconz.service
+  capabilities =
+    lib.optionals (cfg.httpPort < 1024 || cfg.wsPort < 1024) [ "CAP_NET_BIND_SERVICE" ]
+    ++ lib.optionals (cfg.allowRebootSystem) [ "CAP_SYS_BOOT" ]
+    ++ lib.optionals (cfg.allowRestartService) [ "CAP_KILL" ]
+    ++ lib.optionals (cfg.allowSetSystemTime) [ "CAP_SYS_TIME" ];
+in
+{
+  options.services.deconz = {
+
+    enable = lib.mkEnableOption "deCONZ, a Zigbee gateway for use with ConBee hardware (https://phoscon.de/en/conbee2)";
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.deconz;
+      defaultText = lib.literalExpression "pkgs.deconz";
+      description = "Which deCONZ package to use.";
+    };
+
+    device = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = ''
+        Force deCONZ to use a specific USB device (e.g. /dev/ttyACM0). By
+        default it does a search.
+      '';
+    };
+
+    listenAddress = lib.mkOption {
+      type = lib.types.str;
+      default = "127.0.0.1";
+      description = ''
+        Pin deCONZ to the network interface specified through the provided IP
+        address. This applies for the webserver as well as the websocket
+        notifications.
+      '';
+    };
+
+    httpPort = lib.mkOption {
+      type = lib.types.port;
+      default = 80;
+      description = "TCP port for the web server.";
+    };
+
+    wsPort = lib.mkOption {
+      type = lib.types.port;
+      default = 443;
+      description = "TCP port for the WebSocket.";
+    };
+
+    openFirewall = lib.mkEnableOption "opening up the service ports in the firewall";
+
+    allowRebootSystem = lib.mkEnableOption "rebooting the system";
+
+    allowRestartService = lib.mkEnableOption "killing/restarting processes";
+
+    allowSetSystemTime = lib.mkEnableOption "setting the system time";
+
+    extraArgs = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ ];
+      example = [
+        "--dbg-info=1"
+        "--dbg-err=2"
+      ];
+      description = ''
+        Extra command line arguments for deCONZ, see
+        https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/deCONZ-command-line-parameters.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
+      cfg.httpPort
+      cfg.wsPort
+    ];
+
+    services.udev.packages = [ cfg.package ];
+
+    systemd.services.deconz = {
+      description = "deCONZ Zigbee gateway";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        # The service puts a nix store path reference in here, and that path can
+        # be garbage collected. Ensure the file gets "refreshed" on every start.
+        rm -f ${stateDir}/.local/share/dresden-elektronik/deCONZ/zcldb.txt
+      '';
+      environment = {
+        HOME = stateDir;
+        XDG_RUNTIME_DIR = "/run/${name}";
+      };
+      serviceConfig = {
+        ExecStart =
+          "${lib.getExe cfg.package}"
+          + " -platform minimal"
+          + " --http-listen=${cfg.listenAddress}"
+          + " --http-port=${toString cfg.httpPort}"
+          + " --ws-port=${toString cfg.wsPort}"
+          + " --auto-connect=1"
+          + (lib.optionalString (cfg.device != null) " --dev=${cfg.device}")
+          + " " + (lib.escapeShellArgs cfg.extraArgs);
+        Restart = "on-failure";
+        AmbientCapabilities = capabilities;
+        CapabilityBoundingSet = capabilities;
+        UMask = "0027";
+        DynamicUser = true;
+        RuntimeDirectory = name;
+        RuntimeDirectoryMode = "0700";
+        StateDirectory = name;
+        WorkingDirectory = stateDir;
+        # For access to /dev/ttyACM0 (ConBee).
+        SupplementaryGroups = [ "dialout" ];
+        ProtectHome = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 4886654e8c0..14bbe334e50 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.services.dnsmasq;
-  dnsmasq = pkgs.dnsmasq;
+  dnsmasq = cfg.package;
   stateDir = "/var/lib/dnsmasq";
 
   # True values are just put as `name` instead of `name=true`, and false values
@@ -53,6 +53,8 @@ in
         '';
       };
 
+      package = mkPackageOptionMD pkgs "dnsmasq" {};
+
       resolveLocalQueries = mkOption {
         type = types.bool;
         default = true;
diff --git a/nixos/modules/services/networking/fastnetmon-advanced.nix b/nixos/modules/services/networking/fastnetmon-advanced.nix
new file mode 100644
index 00000000000..26e8ad8b76d
--- /dev/null
+++ b/nixos/modules/services/networking/fastnetmon-advanced.nix
@@ -0,0 +1,222 @@
+{ config, lib, pkgs, ... }:
+
+let
+  # Background information: FastNetMon requires a MongoDB to start. This is because
+  # it uses MongoDB to store its configuration. That is, in a normal setup there is
+  # one collection with one document.
+  # To provide declarative configuration in our NixOS module, this database is
+  # completely emptied and replaced on each boot by the fastnetmon-setup service
+  # using the configuration backup functionality.
+
+  cfg = config.services.fastnetmon-advanced;
+  settingsFormat = pkgs.formats.yaml { };
+
+  # obtain the default configs by starting up ferretdb and fcli in a derivation
+  default_configs = pkgs.runCommand "default-configs" {
+    nativeBuildInputs = [
+      pkgs.ferretdb
+      pkgs.fastnetmon-advanced # for fcli
+      pkgs.proot
+    ];
+  } ''
+    mkdir ferretdb fastnetmon $out
+    FERRETDB_TELEMETRY="disable" FERRETDB_HANDLER="sqlite" FERRETDB_STATE_DIR="$PWD/ferretdb" FERRETDB_SQLITE_URL="file:$PWD/ferretdb/" ferretdb &
+
+    cat << EOF > fastnetmon/fastnetmon.conf
+    ${builtins.toJSON {
+      mongodb_username = "";
+    }}
+    EOF
+    proot -b fastnetmon:/etc/fastnetmon -0 fcli create_configuration
+    proot -b fastnetmon:/etc/fastnetmon -0 fcli set bgp default
+    proot -b fastnetmon:/etc/fastnetmon -0 fcli export_configuration backup.tar
+    tar -C $out --no-same-owner -xvf backup.tar
+  '';
+
+  # merge the user configs into the default configs
+  config_tar = pkgs.runCommand "fastnetmon-config.tar" {
+    nativeBuildInputs = with pkgs; [ jq ];
+  } ''
+    jq -s add ${default_configs}/main.json ${pkgs.writeText "main-add.json" (builtins.toJSON cfg.settings)} > main.json
+    mkdir hostgroup
+    ${lib.concatImapStringsSep "\n" (pos: hostgroup: ''
+      jq -s add ${default_configs}/hostgroup/0.json ${pkgs.writeText "hostgroup-${toString (pos - 1)}-add.json" (builtins.toJSON hostgroup)} > hostgroup/${toString (pos - 1)}.json
+    '') hostgroups}
+    mkdir bgp
+    ${lib.concatImapStringsSep "\n" (pos: bgp: ''
+      jq -s add ${default_configs}/bgp/0.json ${pkgs.writeText "bgp-${toString (pos - 1)}-add.json" (builtins.toJSON bgp)} > bgp/${toString (pos - 1)}.json
+    '') bgpPeers}
+    tar -cf $out main.json ${lib.concatImapStringsSep " " (pos: _: "hostgroup/${toString (pos - 1)}.json") hostgroups} ${lib.concatImapStringsSep " " (pos: _: "bgp/${toString (pos - 1)}.json") bgpPeers}
+  '';
+
+  hostgroups = lib.mapAttrsToList (name: hostgroup: { inherit name; } // hostgroup) cfg.hostgroups;
+  bgpPeers = lib.mapAttrsToList (name: bgpPeer: { inherit name; } // bgpPeer) cfg.bgpPeers;
+
+in {
+  options.services.fastnetmon-advanced = with lib; {
+    enable = mkEnableOption "the fastnetmon-advanced DDoS Protection daemon";
+
+    settings = mkOption {
+      description = ''
+        Extra configuration options to declaratively load into FastNetMon Advanced.
+
+        See the [FastNetMon Advanced Configuration options reference](https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-configuration-options/) for more details.
+      '';
+      type = settingsFormat.type;
+      default = {};
+      example = literalExpression ''
+        {
+          networks_list = [ "192.0.2.0/24" ];
+          gobgp = true;
+          gobgp_flow_spec_announces = true;
+        }
+      '';
+    };
+    hostgroups = mkOption {
+      description = "Hostgroups to declaratively load into FastNetMon Advanced";
+      type = types.attrsOf settingsFormat.type;
+      default = {};
+    };
+    bgpPeers = mkOption {
+      description = "BGP Peers to declaratively load into FastNetMon Advanced";
+      type = types.attrsOf settingsFormat.type;
+      default = {};
+    };
+
+    enableAdvancedTrafficPersistence = mkOption {
+      description = "Store historical flow data in clickhouse";
+      type = types.bool;
+      default = false;
+    };
+
+    traffic_db.settings = mkOption {
+      type = settingsFormat.type;
+      description = "Additional settings for /etc/fastnetmon/traffic_db.conf";
+    };
+  };
+
+  config = lib.mkMerge [ (lib.mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [
+      fastnetmon-advanced # for fcli
+    ];
+
+    environment.etc."fastnetmon/license.lic".source = "/var/lib/fastnetmon/license.lic";
+    environment.etc."fastnetmon/gobgpd.conf".source = "/run/fastnetmon/gobgpd.conf";
+    environment.etc."fastnetmon/fastnetmon.conf".source = pkgs.writeText "fastnetmon.conf" (builtins.toJSON {
+      mongodb_username = "";
+    });
+
+    services.ferretdb.enable = true;
+
+    systemd.services.fastnetmon-setup = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "ferretdb.service" ];
+      path = with pkgs; [ fastnetmon-advanced config.systemd.package ];
+      script = ''
+        fcli create_configuration
+        fcli delete hostgroup global
+        fcli import_configuration ${config_tar}
+        systemctl --no-block try-restart fastnetmon
+      '';
+      serviceConfig.Type = "oneshot";
+    };
+
+    systemd.services.fastnetmon = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "ferretdb.service" "fastnetmon-setup.service" "polkit.service" ];
+      path = with pkgs; [ iproute2 ];
+      unitConfig = {
+        # Disable logic which shuts service when we do too many restarts
+        # We do restarts from sudo fcli commit and it's expected that we may have many restarts
+        # Details: https://github.com/systemd/systemd/issues/2416
+        StartLimitInterval = 0;
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.fastnetmon-advanced}/bin/fastnetmon --log_to_console";
+
+        LimitNOFILE = 65535;
+        # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened
+        Restart= "on-failure";
+        RestartSec= "5s";
+
+        DynamicUser = true;
+        CacheDirectory = "fastnetmon";
+        RuntimeDirectory = "fastnetmon"; # for gobgpd config
+        StateDirectory = "fastnetmon"; # for license file
+      };
+    };
+
+    security.polkit.enable = true;
+    security.polkit.extraConfig = ''
+      polkit.addRule(function(action, subject) {
+        if (action.id == "org.freedesktop.systemd1.manage-units" &&
+          subject.isInGroup("fastnetmon")) {
+          if (action.lookup("unit") == "gobgp.service") {
+            var verb = action.lookup("verb");
+            if (verb == "start" || verb == "stop" || verb == "restart") {
+              return polkit.Result.YES;
+            }
+          }
+        }
+      });
+    '';
+
+    # We don't use the existing gobgp NixOS module and package, because the gobgp
+    # version might not be compatible with fastnetmon. Also, the service name
+    # _must_ be 'gobgp' and not 'gobgpd', so that fastnetmon can reload the config.
+    systemd.services.gobgp = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = "GoBGP Routing Daemon";
+      unitConfig = {
+        ConditionPathExists = "/run/fastnetmon/gobgpd.conf";
+      };
+      serviceConfig = {
+        Type = "notify";
+        ExecStartPre = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf -d";
+        SupplementaryGroups = [ "fastnetmon" ];
+        ExecStart = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf --sdnotify";
+        ExecReload = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -r";
+        DynamicUser = true;
+        AmbientCapabilities = "cap_net_bind_service";
+      };
+    };
+  })
+
+  (lib.mkIf (cfg.enable && cfg.enableAdvancedTrafficPersistence) {
+    ## Advanced Traffic persistence
+    ## https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-traffic-persistency/
+
+    services.clickhouse.enable = true;
+
+    services.fastnetmon-advanced.settings.traffic_db = true;
+
+    services.fastnetmon-advanced.traffic_db.settings = {
+      clickhouse_batch_size = lib.mkDefault 1000;
+      clickhouse_batch_delay = lib.mkDefault 1;
+      traffic_db_host = lib.mkDefault "127.0.0.1";
+      traffic_db_port = lib.mkDefault 8100;
+      clickhouse_host = lib.mkDefault "127.0.0.1";
+      clickhouse_port = lib.mkDefault 9000;
+      clickhouse_user = lib.mkDefault "default";
+      clickhouse_password = lib.mkDefault "";
+    };
+    environment.etc."fastnetmon/traffic_db.conf".text = builtins.toJSON cfg.traffic_db.settings;
+
+    systemd.services.traffic_db = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.fastnetmon-advanced}/bin/traffic_db";
+        # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened
+        Restart= "on-failure";
+        RestartSec= "5s";
+
+        DynamicUser = true;
+      };
+    };
+
+  }) ];
+
+  meta.maintainers = lib.teams.wdz.members;
+}
diff --git a/nixos/modules/services/networking/firefox-syncserver.md b/nixos/modules/services/networking/firefox-syncserver.md
index 3ee863343ec..4d8777d204b 100644
--- a/nixos/modules/services/networking/firefox-syncserver.md
+++ b/nixos/modules/services/networking/firefox-syncserver.md
@@ -45,7 +45,7 @@ this instance, and `url`, which holds the URL under which the sync server can be
 accessed. The `url` can be configured automatically when using nginx.
 
 Options that affect the surroundings of the sync server are `enableNginx`,
-`enableTLS` and `hostnam`. If `enableNginx` is set the sync server module will
+`enableTLS` and `hostname`. If `enableNginx` is set the sync server module will
 automatically add an nginx virtual host to the system using `hostname` as the
 domain and set `url` accordingly. If `enableTLS` is set the module will also
 enable ACME certificates on the new virtual host and force all connections to
diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix
index 42924d7f699..71eb2f537ac 100644
--- a/nixos/modules/services/networking/firefox-syncserver.nix
+++ b/nixos/modules/services/networking/firefox-syncserver.nix
@@ -224,10 +224,12 @@ in
           Settings for the sync server. These take priority over values computed
           from NixOS options.
 
-          See the doc comments on the `Settings` structs in
-          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/settings.rs>
+          See the example config in
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/config/local.example.toml>
+          and the doc comments on the `Settings` structs in
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage-settings/src/lib.rs>
           and
-          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/tokenserver/settings.rs>
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/tokenserver-settings/src/lib.rs>
           for available options.
         '';
       };
diff --git a/nixos/modules/services/networking/go-neb.nix b/nixos/modules/services/networking/go-neb.nix
index b65bb5f548e..78d24ecf17d 100644
--- a/nixos/modules/services/networking/go-neb.nix
+++ b/nixos/modules/services/networking/go-neb.nix
@@ -9,7 +9,7 @@ let
   configFile = settingsFormat.generate "config.yaml" cfg.config;
 in {
   options.services.go-neb = {
-    enable = mkEnableOption (lib.mdDoc "Extensible matrix bot written in Go");
+    enable = mkEnableOption (lib.mdDoc "an extensible matrix bot written in Go");
 
     bindAddress = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/networking/gvpe.nix b/nixos/modules/services/networking/gvpe.nix
index 2279ceee2f5..558f499022c 100644
--- a/nixos/modules/services/networking/gvpe.nix
+++ b/nixos/modules/services/networking/gvpe.nix
@@ -29,7 +29,7 @@ let
 
       export PATH=$PATH:${pkgs.iproute2}/sbin
 
-      ip link set $IFNAME up
+      ip link set dev $IFNAME up
       ip address add ${cfg.ipAddress} dev $IFNAME
       ip route add ${cfg.subnet} dev $IFNAME
 
diff --git a/nixos/modules/services/networking/harmonia.nix b/nixos/modules/services/networking/harmonia.nix
index 144fa6c708e..4733165cf7d 100644
--- a/nixos/modules/services/networking/harmonia.nix
+++ b/nixos/modules/services/networking/harmonia.nix
@@ -28,6 +28,8 @@ in
   };
 
   config = lib.mkIf cfg.enable {
+    nix.settings.extra-allowed-users = [ "harmonia" ];
+
     systemd.services.harmonia = {
       description = "harmonia binary cache service";
 
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 4ec066c2ec9..5bd8e1d4d7a 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -116,10 +116,10 @@ in {
   options = {
     services.hostapd = {
       enable = mkEnableOption (mdDoc ''
-        Whether to enable hostapd. hostapd is a user space daemon for access point and
+        hostapd, a user space daemon for access point and
         authentication servers. It implements IEEE 802.11 access point management,
         IEEE 802.1X/WPA/WPA2/EAP Authenticators, RADIUS client, EAP server, and RADIUS
-        authentication server.
+        authentication server
       '');
 
       package = mkPackageOption pkgs "hostapd" {};
@@ -899,25 +899,6 @@ in {
                       '';
                     };
                   };
-
-                  managementFrameProtection = mkOption {
-                    default = "required";
-                    type = types.enum ["disabled" "optional" "required"];
-                    apply = x:
-                      getAttr x {
-                        "disabled" = 0;
-                        "optional" = 1;
-                        "required" = 2;
-                      };
-                    description = mdDoc ''
-                      Management frame protection (MFP) authenticates management frames
-                      to prevent deauthentication (or related) attacks.
-
-                      - {var}`"disabled"`: No management frame protection
-                      - {var}`"optional"`: Use MFP if a connection allows it
-                      - {var}`"required"`: Force MFP for all clients
-                    '';
-                  };
                 };
 
                 config = let
@@ -943,7 +924,8 @@ in {
 
                     # IEEE 802.11i (authentication) related configuration
                     # Encrypt management frames to protect against deauthentication and similar attacks
-                    ieee80211w = bssCfg.managementFrameProtection;
+                    ieee80211w = mkDefault 1;
+                    sae_require_mfp = mkDefault 1;
 
                     # Only allow WPA by default and disable insecure WEP
                     auth_algs = mkDefault 1;
@@ -1185,14 +1167,6 @@ in {
                   message = ''hostapd radio ${radio} bss ${bss}: bssid must be specified manually (for now) since this radio uses multiple BSS.'';
                 }
                 {
-                  assertion = auth.mode == "wpa3-sae" -> bssCfg.managementFrameProtection == 2;
-                  message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE which requires managementFrameProtection="required"'';
-                }
-                {
-                  assertion = auth.mode == "wpa3-sae-transition" -> bssCfg.managementFrameProtection != 0;
-                  message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE in transition mode with WPA2-SHA256, which requires managementFrameProtection="optional" or ="required"'';
-                }
-                {
                   assertion = countWpaPasswordDefinitions <= 1;
                   message = ''hostapd radio ${radio} bss ${bss}: must use at most one WPA password option (wpaPassword, wpaPasswordFile, wpaPskFile)'';
                 }
diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix
index 82c144236f3..49b2bef90a5 100644
--- a/nixos/modules/services/networking/hylafax/options.nix
+++ b/nixos/modules/services/networking/hylafax/options.nix
@@ -272,18 +272,18 @@ in
     };
 
     faxcron.enable.spoolInit = mkEnableOption (lib.mdDoc ''
-      Purge old files from the spooling area with
+      purging old files from the spooling area with
       {file}`faxcron`
-      each time the spooling area is initialized.
+      each time the spooling area is initialized
     '');
     faxcron.enable.frequency = mkOption {
       type = nullOr nonEmptyStr;
       default = null;
       example = "daily";
       description = lib.mdDoc ''
-        Purge old files from the spooling area with
+        purging old files from the spooling area with
         {file}`faxcron` with the given frequency
-        (see systemd.time(7)).
+        (see systemd.time(7))
       '';
     };
     faxcron.infoDays = mkOption {
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index c940324ad09..808e7b66d36 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -239,7 +239,7 @@ in
       enable = mkEnableOption (lib.mdDoc "I2Pd daemon") // {
         description = lib.mdDoc ''
           Enables I2Pd as a running service upon activation.
-          Please read http://i2pd.readthedocs.io/en/latest/ for further
+          Please read <https://i2pd.readthedocs.io/en/latest/> for further
           configuration help.
         '';
       };
@@ -265,7 +265,7 @@ in
         '';
       };
 
-      logCLFTime = mkEnableOption (lib.mdDoc "Full CLF-formatted date and time to log");
+      logCLFTime = mkEnableOption (lib.mdDoc "full CLF-formatted date and time to log");
 
       address = mkOption {
         type = with types; nullOr str;
@@ -456,7 +456,7 @@ in
         '';
       };
 
-      trust.enable = mkEnableOption (lib.mdDoc "Explicit trust options");
+      trust.enable = mkEnableOption (lib.mdDoc "explicit trust options");
 
       trust.family = mkOption {
         type = with types; nullOr str;
@@ -474,7 +474,7 @@ in
         '';
       };
 
-      trust.hidden = mkEnableOption (lib.mdDoc "Router concealment");
+      trust.hidden = mkEnableOption (lib.mdDoc "router concealment");
 
       websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
 
@@ -552,7 +552,7 @@ in
 
       proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
 
-        auth = mkEnableOption (lib.mdDoc "Webconsole authentication");
+        auth = mkEnableOption (lib.mdDoc "webconsole authentication");
 
         user = mkOption {
           type = types.str;
diff --git a/nixos/modules/services/networking/ircd-hybrid/builder.sh b/nixos/modules/services/networking/ircd-hybrid/builder.sh
index d9d2e4264df..07a3788abf7 100644
--- a/nixos/modules/services/networking/ircd-hybrid/builder.sh
+++ b/nixos/modules/services/networking/ircd-hybrid/builder.sh
@@ -1,4 +1,4 @@
-if [ -e .attrs.sh ]; then source .attrs.sh; fi
+if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; elif [ -f .attrs.sh ]; then . .attrs.sh; fi
 source $stdenv/setup
 
 doSub() {
diff --git a/nixos/modules/services/networking/iscsi/initiator.nix b/nixos/modules/services/networking/iscsi/initiator.nix
index d2865a660ea..6c30f89b796 100644
--- a/nixos/modules/services/networking/iscsi/initiator.nix
+++ b/nixos/modules/services/networking/iscsi/initiator.nix
@@ -7,7 +7,7 @@ in
     enable = mkEnableOption (lib.mdDoc "the openiscsi iscsi daemon");
     enableAutoLoginOut = mkEnableOption (lib.mdDoc ''
       automatic login and logout of all automatic targets.
-      You probably do not want this.
+      You probably do not want this
     '');
     discoverPortal = mkOption {
       type = nullOr str;
@@ -52,25 +52,27 @@ in
     '';
     environment.etc."iscsi/initiatorname.iscsi".text = "InitiatorName=${cfg.name}";
 
-    system.activationScripts.iscsid = let
-      extraCfgDumper = optionalString (cfg.extraConfigFile != null) ''
-        if [ -f "${cfg.extraConfigFile}" ]; then
-          printf "\n# The following is from ${cfg.extraConfigFile}:\n"
-          cat "${cfg.extraConfigFile}"
-        else
-          echo "Warning: services.openiscsi.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2
-        fi
-      '';
-    in ''
-      (
-        cat ${config.environment.etc."iscsi/iscsid.conf.fragment".source}
-        ${extraCfgDumper}
-      ) > /etc/iscsi/iscsid.conf
-    '';
-
     systemd.packages = [ cfg.package ];
 
-    systemd.services."iscsid".wantedBy = [ "multi-user.target" ];
+    systemd.services."iscsid" = {
+      wantedBy = [ "multi-user.target" ];
+      preStart =
+        let
+          extraCfgDumper = optionalString (cfg.extraConfigFile != null) ''
+            if [ -f "${cfg.extraConfigFile}" ]; then
+              printf "\n# The following is from ${cfg.extraConfigFile}:\n"
+              cat "${cfg.extraConfigFile}"
+            else
+              echo "Warning: services.openiscsi.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2
+            fi
+          '';
+        in ''
+          (
+            cat ${config.environment.etc."iscsi/iscsid.conf.fragment".source}
+            ${extraCfgDumper}
+          ) > /etc/iscsi/iscsid.conf
+        '';
+    };
     systemd.sockets."iscsid".wantedBy = [ "sockets.target" ];
 
     systemd.services."iscsi" = mkIf cfg.enableAutoLoginOut {
diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix
index 945f4113bd4..2f922a026a3 100644
--- a/nixos/modules/services/networking/kea.nix
+++ b/nixos/modules/services/networking/kea.nix
@@ -254,7 +254,6 @@ in
       DynamicUser = true;
       User = "kea";
       ConfigurationDirectory = "kea";
-      RuntimeDirectory = "kea";
       StateDirectory = "kea";
       UMask = "0077";
     };
@@ -289,8 +288,8 @@ in
       ];
 
       environment = {
-        KEA_PIDFILE_DIR = "/run/kea";
-        KEA_LOCKFILE_DIR = "/run/kea";
+        KEA_PIDFILE_DIR = "/run/kea-ctrl-agent";
+        KEA_LOCKFILE_DIR = "/run/kea-ctrl-agent";
       };
 
       restartTriggers = [
@@ -301,6 +300,7 @@ in
         ExecStart = "${package}/bin/kea-ctrl-agent -c /etc/kea/ctrl-agent.conf ${lib.escapeShellArgs cfg.ctrl-agent.extraArgs}";
         KillMode = "process";
         Restart = "on-failure";
+        RuntimeDirectory = "kea-ctrl-agent";
       } // commonServiceConfig;
     };
   })
@@ -329,8 +329,8 @@ in
       ];
 
       environment = {
-        KEA_PIDFILE_DIR = "/run/kea";
-        KEA_LOCKFILE_DIR = "/run/kea";
+        KEA_PIDFILE_DIR = "/run/kea-dhcp4";
+        KEA_LOCKFILE_DIR = "/run/kea-dhcp4";
       };
 
       restartTriggers = [
@@ -348,6 +348,7 @@ in
           "CAP_NET_BIND_SERVICE"
           "CAP_NET_RAW"
         ];
+        RuntimeDirectory = "kea-dhcp4";
       } // commonServiceConfig;
     };
   })
@@ -376,8 +377,8 @@ in
       ];
 
       environment = {
-        KEA_PIDFILE_DIR = "/run/kea";
-        KEA_LOCKFILE_DIR = "/run/kea";
+        KEA_PIDFILE_DIR = "/run/kea-dhcp6";
+        KEA_LOCKFILE_DIR = "/run/kea-dhcp6";
       };
 
       restartTriggers = [
@@ -393,6 +394,7 @@ in
         CapabilityBoundingSet = [
           "CAP_NET_BIND_SERVICE"
         ];
+        RuntimeDirectory = "kea-dhcp6";
       } // commonServiceConfig;
     };
   })
@@ -421,8 +423,8 @@ in
       ];
 
       environment = {
-        KEA_PIDFILE_DIR = "/run/kea";
-        KEA_LOCKFILE_DIR = "/run/kea";
+        KEA_PIDFILE_DIR = "/run/kea-dhcp-ddns";
+        KEA_LOCKFILE_DIR = "/run/kea-dhcp-ddns";
       };
 
       restartTriggers = [
@@ -437,6 +439,7 @@ in
         CapabilityBoundingSet = [
           "CAP_NET_BIND_SERVICE"
         ];
+        RuntimeDirectory = "kea-dhcp-ddns";
       } // commonServiceConfig;
     };
   })
diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix
index d98c0ce25bf..4f6ac945cf9 100644
--- a/nixos/modules/services/networking/knot.nix
+++ b/nixos/modules/services/networking/knot.nix
@@ -103,11 +103,15 @@ let
   in result;
 
   configFile = if cfg.settingsFile != null then
-    assert cfg.settings == {} && cfg.keyFiles == [];
+    # Note: with extraConfig, the 23.05 compat code did include keyFiles from settingsFile.
+    assert cfg.settings == {} && (cfg.keyFiles == [] || cfg.extraConfig != null);
     cfg.settingsFile
-  else pkgs.writeTextFile {
+  else
+    mkConfigFile yamlConfig;
+
+  mkConfigFile = configString: pkgs.writeTextFile {
     name = "knot.conf";
-    text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + yamlConfig;
+    text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + configString;
     # TODO: maybe we could do some checks even when private keys complicate this?
     checkPhase = lib.optionalString (cfg.keyFiles == []) ''
       ${cfg.package}/bin/knotc --config=$out conf-check
@@ -174,7 +178,7 @@ in {
         description = lib.mdDoc ''
           As alternative to ``settings``, you can provide whole configuration
           directly in the almost-YAML format of Knot DNS.
-          You might want to utilize ``writeTextFile`` for this.
+          You might want to utilize ``pkgs.writeText "knot.conf" "longConfigString"`` for this.
         '';
       };
 
@@ -189,9 +193,9 @@ in {
     };
   };
   imports = [
-    # Compatibility with NixOS 23.05.  At least partial, as it fails assert if used with keyFiles.
+    # Compatibility with NixOS 23.05.
     (mkChangedOptionModule [ "services" "knot" "extraConfig" ] [ "services" "knot" "settingsFile" ]
-      (config: pkgs.writeText "knot.conf" config.services.knot.extraConfig)
+      (config: mkConfigFile config.services.knot.extraConfig)
     )
   ];
 
diff --git a/nixos/modules/services/networking/mullvad-vpn.nix b/nixos/modules/services/networking/mullvad-vpn.nix
index 82e68bf92af..8c7d5237971 100644
--- a/nixos/modules/services/networking/mullvad-vpn.nix
+++ b/nixos/modules/services/networking/mullvad-vpn.nix
@@ -65,7 +65,9 @@ with lib;
         pkgs.iproute2
         # Needed for ping
         "/run/wrappers"
-      ];
+        # See https://github.com/NixOS/nixpkgs/issues/262681
+      ] ++ (lib.optional config.networking.resolvconf.enable
+        config.networking.resolvconf.package);
       startLimitBurst = 5;
       startLimitIntervalSec = 20;
       serviceConfig = {
@@ -76,5 +78,5 @@ with lib;
     };
   };
 
-  meta.maintainers = with maintainers; [ patricksjackson ymarkus ];
+  meta.maintainers = with maintainers; [ arcuru ymarkus ];
 }
diff --git a/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix
index bd403e109c2..9099cbe0cd3 100644
--- a/nixos/modules/services/networking/multipath.nix
+++ b/nixos/modules/services/networking/multipath.nix
@@ -546,8 +546,9 @@ in {
     # We do not have systemd in stage-1 boot so must invoke `multipathd`
     # with the `-1` argument which disables systemd calls. Invoke `multipath`
     # to display the multipath mappings in the output of `journalctl -b`.
+    # TODO: Implement for systemd stage 1
     boot.initrd.kernelModules = [ "dm-multipath" "dm-service-time" ];
-    boot.initrd.postDeviceCommands = ''
+    boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       modprobe -a dm-multipath dm-service-time
       multipathd -s
       (set -x && sleep 1 && multipath -ll)
diff --git a/nixos/modules/services/networking/nar-serve.nix b/nixos/modules/services/networking/nar-serve.nix
index beee53c8a24..b8b76120e44 100644
--- a/nixos/modules/services/networking/nar-serve.nix
+++ b/nixos/modules/services/networking/nar-serve.nix
@@ -10,7 +10,7 @@ in
   };
   options = {
     services.nar-serve = {
-      enable = mkEnableOption (lib.mdDoc "Serve NAR file contents via HTTP");
+      enable = mkEnableOption (lib.mdDoc "serving NAR file contents via HTTP");
 
       port = mkOption {
         type = types.port;
diff --git a/nixos/modules/services/networking/netclient.nix b/nixos/modules/services/networking/netclient.nix
new file mode 100644
index 00000000000..124735fd716
--- /dev/null
+++ b/nixos/modules/services/networking/netclient.nix
@@ -0,0 +1,27 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.netclient;
+in
+{
+  meta.maintainers = with lib.maintainers; [ wexder ];
+
+  options.services.netclient = {
+    enable = lib.mkEnableOption (lib.mdDoc "Netclient Daemon");
+    package = lib.mkPackageOptionMD pkgs "netclient" { };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    systemd.services.netclient = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      description = "Netclient Daemon";
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${lib.getExe cfg.package} daemon";
+        Restart = "on-failure";
+        RestartSec = "15s";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 53c847ee3ca..d32712c8243 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.networking.networkmanager;
+  ini = pkgs.formats.ini { };
 
   delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [ ];
 
@@ -379,6 +380,74 @@ in
           https://modemmanager.org/docs/modemmanager/fcc-unlock/#integration-with-third-party-fcc-unlock-tools.
         '';
       };
+      ensureProfiles = {
+        profiles = with lib.types; mkOption {
+          type = attrsOf (submodule {
+            freeformType = ini.type;
+
+            options = {
+              connection = {
+                id = lib.mkOption {
+                  type = str;
+                  description = "This is the name that will be displayed by NetworkManager and GUIs.";
+                };
+                type = lib.mkOption {
+                  type = str;
+                  description = "The connection type defines the connection kind, like vpn, wireguard, gsm, wifi and more.";
+                  example = "vpn";
+                };
+              };
+            };
+          });
+          apply = (lib.filterAttrsRecursive (n: v: v != { }));
+          default = { };
+          example = {
+            home-wifi = {
+              connection = {
+                id = "home-wifi";
+                type = "wifi";
+                permissions = "";
+              };
+              wifi = {
+                mac-address-blacklist = "";
+                mode = "infrastructure";
+                ssid = "Home Wi-Fi";
+              };
+              wifi-security = {
+                auth-alg = "open";
+                key-mgmt = "wpa-psk";
+                psk = "$HOME_WIFI_PASSWORD";
+              };
+              ipv4 = {
+                dns-search = "";
+                method = "auto";
+              };
+              ipv6 = {
+                addr-gen-mode = "stable-privacy";
+                dns-search = "";
+                method = "auto";
+              };
+            };
+          };
+          description = lib.mdDoc ''
+            Declaratively define NetworkManager profiles. You can find information about the generated file format [here](https://networkmanager.dev/docs/api/latest/nm-settings-keyfile.html) and [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/assembly_networkmanager-connection-profiles-in-keyfile-format_configuring-and-managing-networking).
+            You current profiles which are most likely stored in `/etc/NetworkManager/system-connections` and there is [a tool](https://github.com/janik-haag/nm2nix) to convert them to the needed nix code.
+            If you add a new ad-hoc connection via a GUI or nmtui or anything similar it should just work together with the declarative ones.
+            And if you edit a declarative profile NetworkManager will move it to the persistent storage and treat it like a ad-hoc one,
+            but there will be two profiles as soon as the systemd unit from this option runs again which can be confusing since NetworkManager tools will start displaying two profiles with the same name and probably a bit different settings depending on what you edited.
+            A profile won't be deleted even if it's removed from the config until the system reboots because that's when NetworkManager clears it's temp directory.
+          '';
+        };
+        environmentFiles = mkOption {
+          default = [];
+          type = types.listOf types.path;
+          example = [ "/run/secrets/network-manager.env" ];
+          description = lib.mdDoc ''
+            Files to load as environment file. Environment variables from this file
+            will be substituted into the static configuration file using [envsubst](https://github.com/a8m/envsubst).
+          '';
+        };
+      };
     };
   };
 
@@ -507,6 +576,30 @@ in
       aliases = [ "dbus-org.freedesktop.nm-dispatcher.service" ];
     };
 
+    systemd.services.NetworkManager-ensure-profiles = mkIf (cfg.ensureProfiles.profiles != { }) {
+      description = "Ensure that NetworkManager declarative profiles are created";
+      wantedBy = [ "multi-user.target" ];
+      before = [ "network-online.target" ];
+      script = let
+        path = id: "/run/NetworkManager/system-connections/${id}.nmconnection";
+      in ''
+        mkdir -p /run/NetworkManager/system-connections
+      '' + lib.concatMapStringsSep "\n"
+        (profile: ''
+          ${pkgs.envsubst}/bin/envsubst -i ${ini.generate (lib.escapeShellArg profile.n) profile.v} > ${path (lib.escapeShellArg profile.n)}
+        '') (lib.mapAttrsToList (n: v: { inherit n v; }) cfg.ensureProfiles.profiles)
+      + ''
+        if systemctl is-active --quiet NetworkManager; then
+          ${pkgs.networkmanager}/bin/nmcli connection reload
+        fi
+      '';
+      serviceConfig = {
+        EnvironmentFile = cfg.ensureProfiles.environmentFiles;
+        UMask = "0177";
+        Type = "oneshot";
+      };
+    };
+
     # Turn off NixOS' network management when networking is managed entirely by NetworkManager
     networking = mkMerge [
       (mkIf (!delegateWireless) {
diff --git a/nixos/modules/services/networking/nftables.nix b/nixos/modules/services/networking/nftables.nix
index a0afdb45275..424d005dc0b 100644
--- a/nixos/modules/services/networking/nftables.nix
+++ b/nixos/modules/services/networking/nftables.nix
@@ -103,7 +103,7 @@ in
       '';
     };
 
-    networking.nftables.flushRuleset = mkEnableOption (lib.mdDoc "Flush the entire ruleset on each reload.");
+    networking.nftables.flushRuleset = mkEnableOption (lib.mdDoc "flushing the entire ruleset on each reload");
 
     networking.nftables.extraDeletions = mkOption {
       type = types.lines;
diff --git a/nixos/modules/services/networking/nix-serve.nix b/nixos/modules/services/networking/nix-serve.nix
index f37be31270b..8c4352bc95e 100644
--- a/nixos/modules/services/networking/nix-serve.nix
+++ b/nixos/modules/services/networking/nix-serve.nix
@@ -67,6 +67,8 @@ in
   };
 
   config = mkIf cfg.enable {
+    nix.settings.extra-allowed-users = [ "nix-serve" ];
+
     systemd.services.nix-serve = {
       description = "nix-serve binary cache server";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/ntp/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix
index afd721e34da..d370e6946d7 100644
--- a/nixos/modules/services/networking/ntp/chrony.nix
+++ b/nixos/modules/services/networking/ntp/chrony.nix
@@ -9,6 +9,7 @@ let
   stateDir = cfg.directory;
   driftFile = "${stateDir}/chrony.drift";
   keyFile = "${stateDir}/chrony.keys";
+  rtcFile = "${stateDir}/chrony.rtc";
 
   configFile = pkgs.writeText "chrony.conf" ''
     ${concatMapStringsSep "\n" (server: "server " + server + " " + cfg.serverOption + optionalString (cfg.enableNTS) " nts") cfg.servers}
@@ -20,8 +21,10 @@ let
 
     driftfile ${driftFile}
     keyfile ${keyFile}
+    ${optionalString (cfg.enableRTCTrimming) "rtcfile ${rtcFile}"}
     ${optionalString (cfg.enableNTS) "ntsdumpdir ${stateDir}"}
 
+    ${optionalString (cfg.enableRTCTrimming) "rtcautotrim ${builtins.toString cfg.autotrimThreshold}"}
     ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
 
     ${cfg.extraConfig}
@@ -85,6 +88,33 @@ in
         '';
       };
 
+      enableRTCTrimming = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Enable tracking of the RTC offset to the system clock and automatic trimming.
+          See also [](#opt-services.chrony.autotrimThreshold)
+
+          ::: {.note}
+          This is not compatible with the `rtcsync` directive, which naively syncs the RTC time every 11 minutes.
+
+          Tracking the RTC drift will allow more precise timekeeping,
+          especially on intermittently running devices, where the RTC is very relevant.
+          :::
+        '';
+      };
+
+      autotrimThreshold = mkOption {
+        type = types.ints.positive;
+        default = 30;
+        example = 10;
+        description = ''
+          Maximum estimated error threshold for the `rtcautotrim` command.
+          When reached, the RTC will be trimmed.
+          Only used when [](#opt-services.chrony.enableRTCTrimming) is enabled.
+        '';
+      };
+
       enableNTS = mkOption {
         type = types.bool;
         default = false;
@@ -141,7 +171,7 @@ in
   };
 
   config = mkIf cfg.enable {
-    meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+    meta.maintainers = with lib.maintainers; [ thoughtpolice vifino ];
 
     environment.systemPackages = [ chronyPkg ];
 
@@ -156,12 +186,19 @@ in
 
     services.timesyncd.enable = mkForce false;
 
+    # If chrony controls and tracks the RTC, writing it externally causes clock error.
+    systemd.services.save-hwclock = lib.mkIf cfg.enableRTCTrimming {
+      enable = lib.mkForce false;
+    };
+
     systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; };
 
     systemd.tmpfiles.rules = [
       "d ${stateDir} 0750 chrony chrony - -"
       "f ${driftFile} 0640 chrony chrony - -"
       "f ${keyFile} 0640 chrony chrony - -"
+    ] ++ lib.optionals cfg.enableRTCTrimming [
+      "f ${rtcFile} 0640 chrony chrony - -"
     ];
 
     systemd.services.chronyd =
diff --git a/nixos/modules/services/networking/ntp/ntpd.nix b/nixos/modules/services/networking/ntp/ntpd.nix
index 036a8df635d..2bc690cacf0 100644
--- a/nixos/modules/services/networking/ntp/ntpd.nix
+++ b/nixos/modules/services/networking/ntp/ntpd.nix
@@ -56,7 +56,7 @@ in
           The default flags prevent external hosts from using ntpd as a DDoS
           reflector, setting system time, and querying OS/ntpd version. As
           recommended in section 6.5.1.1.3, answer "No" of
-          http://support.ntp.org/bin/view/Support/AccessRestrictions
+          https://support.ntp.org/Support/AccessRestrictions
         '';
         default = [ "limited" "kod" "nomodify" "notrap" "noquery" "nopeer" ];
       };
diff --git a/nixos/modules/services/networking/pleroma.nix b/nixos/modules/services/networking/pleroma.nix
index e9db7f3eab8..89e64d36c8a 100644
--- a/nixos/modules/services/networking/pleroma.nix
+++ b/nixos/modules/services/networking/pleroma.nix
@@ -146,6 +146,6 @@ in {
     };
 
   };
-  meta.maintainers = with lib.maintainers; [ ninjatrappeur ];
+  meta.maintainers = with lib.maintainers; [ picnoir ];
   meta.doc = ./pleroma.md;
 }
diff --git a/nixos/modules/services/networking/prayer.nix b/nixos/modules/services/networking/prayer.nix
deleted file mode 100644
index 197aa8a6f44..00000000000
--- a/nixos/modules/services/networking/prayer.nix
+++ /dev/null
@@ -1,90 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  inherit (pkgs) prayer;
-
-  cfg = config.services.prayer;
-
-  stateDir = "/var/lib/prayer";
-
-  prayerUser = "prayer";
-  prayerGroup = "prayer";
-
-  prayerExtraCfg = pkgs.writeText "extraprayer.cf" ''
-    prefix = "${prayer}"
-    var_prefix = "${stateDir}"
-    prayer_user = "${prayerUser}"
-    prayer_group = "${prayerGroup}"
-    sendmail_path = "/run/wrappers/bin/sendmail"
-
-    use_http_port ${cfg.port}
-
-    ${cfg.extraConfig}
-  '';
-
-  prayerCfg = pkgs.runCommand "prayer.cf" { preferLocalBuild = true; } ''
-    # We have to remove the http_port 80, or it will start a server there
-    cat ${prayer}/etc/prayer.cf | grep -v http_port > $out
-    cat ${prayerExtraCfg} >> $out
-  '';
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.prayer = {
-
-      enable = mkEnableOption (lib.mdDoc "the prayer webmail http server");
-
-      port = mkOption {
-        default = 2080;
-        type = types.port;
-        description = lib.mdDoc ''
-          Port the prayer http server is listening to.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "" ;
-        description = lib.mdDoc ''
-          Extra configuration. Contents will be added verbatim to the configuration file.
-        '';
-      };
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.prayer.enable {
-    environment.systemPackages = [ prayer ];
-
-    users.users.${prayerUser} =
-      { uid = config.ids.uids.prayer;
-        description = "Prayer daemon user";
-        home = stateDir;
-      };
-
-    users.groups.${prayerGroup} =
-      { gid = config.ids.gids.prayer; };
-
-    systemd.services.prayer = {
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig.Type = "forking";
-      preStart = ''
-        mkdir -m 0755 -p ${stateDir}
-        chown ${prayerUser}:${prayerGroup} ${stateDir}
-      '';
-      script = "${prayer}/sbin/prayer --config-file=${prayerCfg}";
-    };
-  };
-}
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index 0066c77438f..038d574bd87 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -779,9 +779,6 @@ in
 
       admins = ${toLua cfg.admins}
 
-      -- we already build with libevent, so we can just enable it for a more performant server
-      use_libevent = true
-
       modules_enabled = {
 
         ${ lib.concatStringsSep "\n  " (lib.mapAttrsToList
diff --git a/nixos/modules/services/networking/rosenpass.nix b/nixos/modules/services/networking/rosenpass.nix
new file mode 100644
index 00000000000..d2a264b83d6
--- /dev/null
+++ b/nixos/modules/services/networking/rosenpass.nix
@@ -0,0 +1,233 @@
+{ config
+, lib
+, options
+, pkgs
+, ...
+}:
+let
+  inherit (lib)
+    attrValues
+    concatLines
+    concatMap
+    filter
+    filterAttrsRecursive
+    flatten
+    getExe
+    mdDoc
+    mkIf
+    optional
+    ;
+
+  cfg = config.services.rosenpass;
+  opt = options.services.rosenpass;
+  settingsFormat = pkgs.formats.toml { };
+in
+{
+  options.services.rosenpass =
+    let
+      inherit (lib)
+        literalExpression
+        mdDoc
+        mkOption
+        ;
+      inherit (lib.types)
+        enum
+        listOf
+        nullOr
+        path
+        str
+        submodule
+        ;
+    in
+    {
+      enable = lib.mkEnableOption (mdDoc "Rosenpass");
+
+      package = lib.mkPackageOption pkgs "rosenpass" { };
+
+      defaultDevice = mkOption {
+        type = nullOr str;
+        description = mdDoc "Name of the network interface to use for all peers by default.";
+        example = "wg0";
+      };
+
+      settings = mkOption {
+        type = submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            public_key = mkOption {
+              type = path;
+              description = mdDoc "Path to a file containing the public key of the local Rosenpass peer. Generate this by running {command}`rosenpass gen-keys`.";
+            };
+
+            secret_key = mkOption {
+              type = path;
+              description = mdDoc "Path to a file containing the secret key of the local Rosenpass peer. Generate this by running {command}`rosenpass gen-keys`.";
+            };
+
+            listen = mkOption {
+              type = listOf str;
+              description = mdDoc "List of local endpoints to listen for connections.";
+              default = [ ];
+              example = literalExpression "[ \"0.0.0.0:10000\" ]";
+            };
+
+            verbosity = mkOption {
+              type = enum [ "Verbose" "Quiet" ];
+              default = "Quiet";
+              description = mdDoc "Verbosity of output produced by the service.";
+            };
+
+            peers =
+              let
+                peer = submodule {
+                  freeformType = settingsFormat.type;
+
+                  options = {
+                    public_key = mkOption {
+                      type = path;
+                      description = mdDoc "Path to a file containing the public key of the remote Rosenpass peer.";
+                    };
+
+                    endpoint = mkOption {
+                      type = nullOr str;
+                      default = null;
+                      description = mdDoc "Endpoint of the remote Rosenpass peer.";
+                    };
+
+                    device = mkOption {
+                      type = str;
+                      default = cfg.defaultDevice;
+                      defaultText = literalExpression "config.${opt.defaultDevice}";
+                      description = mdDoc "Name of the local WireGuard interface to use for this peer.";
+                    };
+
+                    peer = mkOption {
+                      type = str;
+                      description = mdDoc "WireGuard public key corresponding to the remote Rosenpass peer.";
+                    };
+                  };
+                };
+              in
+              mkOption {
+                type = listOf peer;
+                description = mdDoc "List of peers to exchange keys with.";
+                default = [ ];
+              };
+          };
+        };
+        default = { };
+        description = mdDoc "Configuration for Rosenpass, see <https://rosenpass.eu/> for further information.";
+      };
+    };
+
+  config = mkIf cfg.enable {
+    warnings =
+      let
+        # NOTE: In the descriptions below, we tried to refer to e.g.
+        # options.systemd.network.netdevs."<name>".wireguardPeers.*.PublicKey
+        # directly, but don't know how to traverse "<name>" and * in this path.
+        extractions = [
+          {
+            relevant = config.systemd.network.enable;
+            root = config.systemd.network.netdevs;
+            peer = (x: x.wireguardPeers);
+            key = (x: if x.wireguardPeerConfig ? PublicKey then x.wireguardPeerConfig.PublicKey else null);
+            description = mdDoc "${options.systemd.network.netdevs}.\"<name>\".wireguardPeers.*.wireguardPeerConfig.PublicKey";
+          }
+          {
+            relevant = config.networking.wireguard.enable;
+            root = config.networking.wireguard.interfaces;
+            peer = (x: x.peers);
+            key = (x: x.publicKey);
+            description = mdDoc "${options.networking.wireguard.interfaces}.\"<name>\".peers.*.publicKey";
+          }
+          rec {
+            relevant = root != { };
+            root = config.networking.wg-quick.interfaces;
+            peer = (x: x.peers);
+            key = (x: x.publicKey);
+            description = mdDoc "${options.networking.wg-quick.interfaces}.\"<name>\".peers.*.publicKey";
+          }
+        ];
+        relevantExtractions = filter (x: x.relevant) extractions;
+        extract = { root, peer, key, ... }:
+          filter (x: x != null) (flatten (concatMap (x: (map key (peer x))) (attrValues root)));
+        configuredKeys = flatten (map extract relevantExtractions);
+        itemize = xs: concatLines (map (x: " - ${x}") xs);
+        descriptions = map (x: "`${x.description}`");
+        missingKeys = filter (key: !builtins.elem key configuredKeys) (map (x: x.peer) cfg.settings.peers);
+        unusual = ''
+          While this may work as expected, e.g. you want to manually configure WireGuard,
+          such a scenario is unusual. Please double-check your configuration.
+        '';
+      in
+      (optional (relevantExtractions != [ ] && missingKeys != [ ]) ''
+        You have configured Rosenpass peers with the WireGuard public keys:
+        ${itemize missingKeys}
+        But there is no corresponding active Wireguard peer configuration in any of:
+        ${itemize (descriptions relevantExtractions)}
+        ${unusual}
+      '')
+      ++
+      optional (relevantExtractions == [ ]) ''
+        You have configured Rosenpass, but you have not configured Wireguard via any of:
+        ${itemize (descriptions extractions)}
+        ${unusual}
+      '';
+
+    environment.systemPackages = [ cfg.package pkgs.wireguard-tools ];
+
+    systemd.services.rosenpass =
+      let
+        filterNonNull = filterAttrsRecursive (_: v: v != null);
+        config = settingsFormat.generate "config.toml" (
+          filterNonNull (cfg.settings
+            //
+            (
+              let
+                credentialPath = id: "$CREDENTIALS_DIRECTORY/${id}";
+                # NOTE: We would like to remove all `null` values inside `cfg.settings`
+                # recursively, since `settingsFormat.generate` cannot handle `null`.
+                # This would require to traverse both attribute sets and lists recursively.
+                # `filterAttrsRecursive` only recurses into attribute sets, but not
+                # into values that might contain other attribute sets (such as lists,
+                # e.g. `cfg.settings.peers`). Here, we just specialize on `cfg.settings.peers`,
+                # and this may break unexpectedly whenever a `null` value is contained
+                # in a list in `cfg.settings`, other than `cfg.settings.peers`.
+                peersWithoutNulls = map filterNonNull cfg.settings.peers;
+              in
+              {
+                secret_key = credentialPath "pqsk";
+                public_key = credentialPath "pqpk";
+                peers = peersWithoutNulls;
+              }
+            )
+          )
+        );
+      in
+      rec {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network-online.target" ];
+        path = [ cfg.package pkgs.wireguard-tools ];
+
+        serviceConfig = {
+          User = "rosenpass";
+          Group = "rosenpass";
+          RuntimeDirectory = "rosenpass";
+          DynamicUser = true;
+          AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+          LoadCredential = [
+            "pqsk:${cfg.settings.secret_key}"
+            "pqpk:${cfg.settings.public_key}"
+          ];
+        };
+
+        # See <https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers>
+        environment.CONFIG = "%t/${serviceConfig.RuntimeDirectory}/config.toml";
+
+        preStart = "${getExe pkgs.envsubst} -i ${config} -o \"$CONFIG\"";
+        script = "rosenpass exchange-config \"$CONFIG\"";
+      };
+  };
+}
diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix
index 40648c72481..8054f01d705 100644
--- a/nixos/modules/services/networking/searx.nix
+++ b/nixos/modules/services/networking/searx.nix
@@ -43,12 +43,8 @@ in
       [ "services" "searx" "settingsFile" ])
   ];
 
-  ###### interface
-
   options = {
-
     services.searx = {
-
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -149,8 +145,8 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.searx;
-        defaultText = literalExpression "pkgs.searx";
+        default = pkgs.searxng;
+        defaultText = literalExpression "pkgs.searxng";
         description = lib.mdDoc "searx package to use.";
       };
 
@@ -190,21 +186,7 @@ in
 
   };
 
-
-  ###### implementation
-
   config = mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = (cfg.limiterSettings != { }) -> cfg.package.pname == "searxng";
-        message = "services.searx.limiterSettings requires services.searx.package to be searxng.";
-      }
-      {
-        assertion = cfg.redisCreateLocally -> cfg.package.pname == "searxng";
-        message = "services.searx.redisCreateLocally requires services.searx.package to be searxng.";
-      }
-    ];
-
     environment.systemPackages = [ cfg.package ];
 
     users.users.searx =
@@ -245,10 +227,10 @@ in
       };
     };
 
-    systemd.services.uwsgi = mkIf (cfg.runInUwsgi)
-      { requires = [ "searx-init.service" ];
-        after = [ "searx-init.service" ];
-      };
+    systemd.services.uwsgi = mkIf cfg.runInUwsgi {
+      requires = [ "searx-init.service" ];
+      after = [ "searx-init.service" ];
+    };
 
     services.searx.settings = {
       # merge NixOS settings with defaults settings.yml
@@ -256,7 +238,7 @@ in
       redis.url = lib.mkIf cfg.redisCreateLocally "unix://${config.services.redis.servers.searx.unixSocket}";
     };
 
-    services.uwsgi = mkIf (cfg.runInUwsgi) {
+    services.uwsgi = mkIf cfg.runInUwsgi {
       enable = true;
       plugins = [ "python3" ];
 
@@ -270,6 +252,7 @@ in
         enable-threads = true;
         module = "searx.webapp";
         env = [
+          # TODO: drop this as it is only required for searx
           "SEARX_SETTINGS_PATH=${cfg.settingsFile}"
           # searxng compatibility https://github.com/searxng/searxng/issues/1519
           "SEARXNG_SETTINGS_PATH=${cfg.settingsFile}"
diff --git a/nixos/modules/services/networking/snowflake-proxy.nix b/nixos/modules/services/networking/snowflake-proxy.nix
index ca015ed9d44..19b68f1e20b 100644
--- a/nixos/modules/services/networking/snowflake-proxy.nix
+++ b/nixos/modules/services/networking/snowflake-proxy.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.snowflake-proxy = {
-      enable = mkEnableOption (lib.mdDoc "System to defeat internet censorship");
+      enable = mkEnableOption (lib.mdDoc "snowflake-proxy, a system to defeat internet censorship");
 
       broker = mkOption {
         description = lib.mdDoc "Broker URL (default \"https://snowflake-broker.torproject.net/\")";
diff --git a/nixos/modules/services/networking/spiped.nix b/nixos/modules/services/networking/spiped.nix
index 3e01ace54ad..547317dbcbe 100644
--- a/nixos/modules/services/networking/spiped.nix
+++ b/nixos/modules/services/networking/spiped.nix
@@ -197,8 +197,9 @@ in
       script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`";
     };
 
-    system.activationScripts.spiped = optionalString (cfg.config != {})
-      "mkdir -p /var/lib/spiped";
+    systemd.tmpfiles.rules = lib.mkIf (cfg.config != { }) [
+      "d /var/lib/spiped -"
+    ];
 
     # Setup spiped config files
     environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec"
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index bf2f5230c73..f54ce591743 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -12,22 +12,44 @@ let
     then cfgc.package
     else pkgs.buildPackages.openssh;
 
-  # reports boolean as yes / no
-  mkValueStringSshd = with lib; v:
-        if isInt           v then toString v
-        else if isString   v then v
-        else if true  ==   v then "yes"
-        else if false ==   v then "no"
-        else if isList     v then concatStringsSep "," v
-        else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
-
   # dont use the "=" operator
-  settingsFormat = (pkgs.formats.keyValue {
-      mkKeyValue = lib.generators.mkKeyValueDefault {
-      mkValueString = mkValueStringSshd;
-    } " ";});
+  settingsFormat =
+    let
+      # reports boolean as yes / no
+      mkValueString = with lib; v:
+            if isInt           v then toString v
+            else if isString   v then v
+            else if true  ==   v then "yes"
+            else if false ==   v then "no"
+            else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
+
+      base = pkgs.formats.keyValue {
+        mkKeyValue = lib.generators.mkKeyValueDefault { inherit mkValueString; } " ";
+      };
+      # OpenSSH is very inconsistent with options that can take multiple values.
+      # For some of them, they can simply appear multiple times and are appended, for others the
+      # values must be separated by whitespace or even commas.
+      # Consult either sshd_config(5) or, as last resort, the OpehSSH source for parsing
+      # the options at servconf.c:process_server_config_line_depth() to determine the right "mode"
+      # for each. But fortunaly this fact is documented for most of them in the manpage.
+      commaSeparated = [ "Ciphers" "KexAlgorithms" "Macs" ];
+      spaceSeparated = [ "AuthorizedKeysFile" "AllowGroups" "AllowUsers" "DenyGroups" "DenyUsers" ];
+    in {
+      inherit (base) type;
+      generate = name: value:
+        let transformedValue = mapAttrs (key: val:
+          if isList val then
+            if elem key commaSeparated then concatStringsSep "," val
+            else if elem key spaceSeparated then concatStringsSep " " val
+            else throw "list value for unknown key ${key}: ${(lib.generators.toPretty {}) val}"
+          else
+            val
+          ) value;
+        in
+          base.generate name transformedValue;
+    };
 
-  configFile = settingsFormat.generate "sshd.conf-settings" cfg.settings;
+  configFile = settingsFormat.generate "sshd.conf-settings" (filterAttrs (n: v: v != null) cfg.settings);
   sshconf = pkgs.runCommand "sshd.conf-final" { } ''
     cat ${configFile} - >$out <<EOL
     ${cfg.extraConfig}
@@ -74,6 +96,19 @@ let
       };
     };
 
+    options.openssh.authorizedPrincipals = mkOption {
+      type = with types; listOf types.singleLineStr;
+      default = [];
+      description = mdDoc ''
+        A list of verbatim principal names that should be added to the user's
+        authorized principals.
+      '';
+      example = [
+        "example@host"
+        "foo@bar"
+      ];
+    };
+
   };
 
   authKeysFiles = let
@@ -89,6 +124,16 @@ let
     ));
   in listToAttrs (map mkAuthKeyFile usersWithKeys);
 
+  authPrincipalsFiles = let
+    mkAuthPrincipalsFile = u: nameValuePair "ssh/authorized_principals.d/${u.name}" {
+      mode = "0444";
+      text = concatStringsSep "\n" u.openssh.authorizedPrincipals;
+    };
+    usersWithPrincipals = attrValues (flip filterAttrs config.users.users (n: u:
+      length u.openssh.authorizedPrincipals != 0
+    ));
+  in listToAttrs (map mkAuthPrincipalsFile usersWithPrincipals);
+
 in
 
 {
@@ -285,6 +330,14 @@ in
         type = types.submodule ({name, ...}: {
           freeformType = settingsFormat.type;
           options = {
+            AuthorizedPrincipalsFile = mkOption {
+              type = types.str;
+              default = "none"; # upstream default
+              description = lib.mdDoc ''
+                Specifies a file that lists principal names that are accepted for certificate authentication. The default
+                is `"none"`, i.e. not to use	a principals file.
+              '';
+            };
             LogLevel = mkOption {
               type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ];
               default = "INFO"; # upstream default
@@ -400,6 +453,42 @@ in
                 <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
               '';
             };
+            AllowUsers = mkOption {
+              type = with types; nullOr (listOf str);
+              default = null;
+              description = lib.mdDoc ''
+                If specified, login is allowed only for the listed users.
+                See {manpage}`sshd_config(5)` for details.
+              '';
+            };
+            DenyUsers = mkOption {
+              type = with types; nullOr (listOf str);
+              default = null;
+              description = lib.mdDoc ''
+                If specified, login is denied for all listed users. Takes
+                precedence over [](#opt-services.openssh.settings.AllowUsers).
+                See {manpage}`sshd_config(5)` for details.
+              '';
+            };
+            AllowGroups = mkOption {
+              type = with types; nullOr (listOf str);
+              default = null;
+              description = lib.mdDoc ''
+                If specified, login is allowed only for users part of the
+                listed groups.
+                See {manpage}`sshd_config(5)` for details.
+              '';
+            };
+            DenyGroups = mkOption {
+              type = with types; nullOr (listOf str);
+              default = null;
+              description = lib.mdDoc ''
+                If specified, login is denied for all users part of the listed
+                groups. Takes precedence over
+                [](#opt-services.openssh.settings.AllowGroups). See
+                {manpage}`sshd_config(5)` for details.
+              '';
+            };
           };
         });
       };
@@ -444,7 +533,7 @@ in
     services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli";
     services.openssh.sftpServerExecutable = mkDefault "${cfgc.package}/libexec/sftp-server";
 
-    environment.etc = authKeysFiles //
+    environment.etc = authKeysFiles // authPrincipalsFiles //
       { "ssh/moduli".source = cfg.moduliFile;
         "ssh/sshd_config".source = sshconf;
       };
@@ -541,6 +630,8 @@ in
     services.openssh.authorizedKeysFiles =
       [ "%h/.ssh/authorized_keys" "/etc/ssh/authorized_keys.d/%u" ];
 
+    services.openssh.settings.AuthorizedPrincipalsFile = mkIf (authPrincipalsFiles != {}) "/etc/ssh/authorized_principals.d/%u";
+
     services.openssh.extraConfig = mkOrder 0
       ''
         UsePAM yes
@@ -583,7 +674,7 @@ in
           (lport: "sshd -G -T -C lport=${toString lport} -f ${sshconf} > /dev/null")
           cfg.ports}
         ${concatMapStringsSep "\n"
-          (la: "sshd -G -T -C laddr=${la.addr},lport=${toString la.port} -f ${sshconf} > /dev/null")
+          (la: "sshd -G -T -C ${escapeShellArg "laddr=${la.addr},lport=${toString la.port}"} -f ${sshconf} > /dev/null")
           cfg.listenAddresses}
         touch $out
       '')
diff --git a/nixos/modules/services/networking/sslh.nix b/nixos/modules/services/networking/sslh.nix
index daf2f2f3668..dd29db51002 100644
--- a/nixos/modules/services/networking/sslh.nix
+++ b/nixos/modules/services/networking/sslh.nix
@@ -5,81 +5,131 @@ with lib;
 let
   cfg = config.services.sslh;
   user = "sslh";
-  configFile = pkgs.writeText "sslh.conf" ''
-    verbose: ${boolToString cfg.verbose};
-    foreground: true;
-    inetd: false;
-    numeric: false;
-    transparent: ${boolToString cfg.transparent};
-    timeout: "${toString cfg.timeout}";
-
-    listen:
-    (
-      ${
-        concatMapStringsSep ",\n"
-        (addr: ''{ host: "${addr}"; port: "${toString cfg.port}"; }'')
-        cfg.listenAddresses
-      }
-    );
-
-    ${cfg.appendConfig}
-  '';
-  defaultAppendConfig = ''
-    protocols:
-    (
-      { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
-      { name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; },
-      { name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; },
-      { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
-      { name: "tls"; host: "localhost"; port: "443"; probe: "builtin"; },
-      { name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; }
-    );
-  '';
+
+  configFormat = pkgs.formats.libconfig {};
+  configFile = configFormat.generate "sslh.conf" cfg.settings;
 in
+
 {
   imports = [
     (mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ])
+    (mkRenamedOptionModule [ "services" "sslh" "timeout" ] [ "services" "sslh" "settings" "timeout" ])
+    (mkRenamedOptionModule [ "services" "sslh" "transparent" ] [ "services" "sslh" "settings" "transparent" ])
+    (mkRemovedOptionModule [ "services" "sslh" "appendConfig" ] "Use services.sslh.settings instead")
+    (mkChangedOptionModule [ "services" "sslh" "verbose" ] [ "services" "sslh" "settings" "verbose-connections" ]
+      (config: if config.services.sslh.verbose then 1 else 0))
   ];
 
-  options = {
-    services.sslh = {
-      enable = mkEnableOption (lib.mdDoc "sslh");
+  meta.buildDocsInSandbox = false;
 
-      verbose = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Verbose logs.";
-      };
+  options.services.sslh = {
+    enable = mkEnableOption (lib.mdDoc "sslh, protocol demultiplexer");
 
-      timeout = mkOption {
-        type = types.int;
-        default = 2;
-        description = lib.mdDoc "Timeout in seconds.";
-      };
+    method = mkOption {
+      type = types.enum [ "fork" "select" "ev" ];
+      default = "fork";
+      description = lib.mdDoc ''
+        The method to use for handling connections:
 
-      transparent = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Will the services behind sslh (Apache, sshd and so on) see the external IP and ports as if the external world connected directly to them";
-      };
+          - `fork` forks a new process for each incoming connection. It is
+          well-tested and very reliable, but incurs the overhead of many
+          processes.
 
-      listenAddresses = mkOption {
-        type = types.coercedTo types.str singleton (types.listOf types.str);
-        default = [ "0.0.0.0" "[::]" ];
-        description = lib.mdDoc "Listening addresses or hostnames.";
-      };
+          - `select` uses only one thread, which monitors all connections at once.
+          It has lower overhead per connection, but if it stops, you'll lose all
+          connections.
 
-      port = mkOption {
-        type = types.port;
-        default = 443;
-        description = lib.mdDoc "Listening port.";
-      };
+          - `ev` is implemented using libev, it's similar to `select` but
+            scales better to a large number of connections.
+      '';
+    };
+
+    listenAddresses = mkOption {
+      type = with types; coercedTo str singleton (listOf str);
+      default = [ "0.0.0.0" "[::]" ];
+      description = lib.mdDoc "Listening addresses or hostnames.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 443;
+      description = lib.mdDoc "Listening port.";
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = configFormat.type;
+
+        options.timeout = mkOption {
+          type = types.ints.unsigned;
+          default = 2;
+          description = lib.mdDoc "Timeout in seconds.";
+        };
+
+        options.transparent = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether the services behind sslh (Apache, sshd and so on) will see the
+            external IP and ports as if the external world connected directly to
+            them.
+          '';
+        };
+
+        options.verbose-connections = mkOption {
+          type = types.ints.between 0 4;
+          default = 0;
+          description = lib.mdDoc ''
+            Where to log connections information. Possible values are:
+
+             0. don't log anything
+             1. write log to stdout
+             2. write log to syslog
+             3. write log to both stdout and syslog
+             4. write to a log file ({option}`sslh.settings.logfile`)
+          '';
+        };
+
+        options.numeric = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to disable reverse DNS lookups, thus keeping IP
+            address literals in the log.
+          '';
+        };
+
+        options.protocols = mkOption {
+          type = types.listOf configFormat.type;
+          default = [
+            { name = "ssh";     host = "localhost"; port =  "22"; service= "ssh"; }
+            { name = "openvpn"; host = "localhost"; port = "1194"; }
+            { name = "xmpp";    host = "localhost"; port = "5222"; }
+            { name = "http";    host = "localhost"; port =   "80"; }
+            { name = "tls";     host = "localhost"; port =  "443"; }
+            { name = "anyprot"; host = "localhost"; port =  "443"; }
+          ];
+          description = lib.mdDoc ''
+            List of protocols sslh will probe for and redirect.
+            Each protocol entry consists of:
+
+              - `name`: name of the probe.
+
+              - `service`: libwrap service name (see {manpage}`hosts_access(5)`),
 
-      appendConfig = mkOption {
-        type = types.str;
-        default = defaultAppendConfig;
-        description = lib.mdDoc "Verbatim configuration file.";
+              - `host`, `port`: where to connect when this probe succeeds,
+
+              - `log_level`: to log incoming connections,
+
+              - `transparent`: proxy this protocol transparently,
+
+              - etc.
+
+            See the documentation for all options, including probe-specific ones.
+          '';
+        };
       };
+      description = lib.mdDoc "sslh configuration. See {manpage}`sslh(8)` for available settings.";
     };
   };
 
@@ -96,20 +146,29 @@ in
           PermissionsStartOnly = true;
           Restart              = "always";
           RestartSec           = "1s";
-          ExecStart            = "${pkgs.sslh}/bin/sslh -F${configFile}";
+          ExecStart            = "${pkgs.sslh}/bin/sslh-${cfg.method} -F${configFile}";
           KillMode             = "process";
-          AmbientCapabilities  = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID";
+          AmbientCapabilities  = ["CAP_NET_BIND_SERVICE" "CAP_NET_ADMIN" "CAP_SETGID" "CAP_SETUID"];
           PrivateTmp           = true;
           PrivateDevices       = true;
           ProtectSystem        = "full";
           ProtectHome          = true;
         };
       };
+
+      services.sslh.settings = {
+        # Settings defined here are not supposed to be changed: doing so will
+        # break the module, as such you need `lib.mkForce` to override them.
+        foreground = true;
+        inetd = false;
+        listen = map (addr: { host = addr; port = toString cfg.port; }) cfg.listenAddresses;
+      };
+
     })
 
     # code from https://github.com/yrutschle/sslh#transparent-proxy-support
     # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module
-    (mkIf (cfg.enable && cfg.transparent) {
+    (mkIf (cfg.enable && cfg.settings.transparent) {
       # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
       boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1;
       boot.kernel.sysctl."net.ipv4.conf.all.route_localnet"     = 1;
diff --git a/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixos/modules/services/networking/strongswan-swanctl/module.nix
index c51e8ad9f5f..bfea8996972 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/module.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/module.nix
@@ -43,21 +43,21 @@ in  {
 
     # The swanctl command complains when the following directories don't exist:
     # See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctldirectory
-    system.activationScripts.strongswan-swanctl-etc = stringAfter ["etc"] ''
-      mkdir -p '/etc/swanctl/x509'     # Trusted X.509 end entity certificates
-      mkdir -p '/etc/swanctl/x509ca'   # Trusted X.509 Certificate Authority certificates
-      mkdir -p '/etc/swanctl/x509ocsp'
-      mkdir -p '/etc/swanctl/x509aa'   # Trusted X.509 Attribute Authority certificates
-      mkdir -p '/etc/swanctl/x509ac'   # Attribute Certificates
-      mkdir -p '/etc/swanctl/x509crl'  # Certificate Revocation Lists
-      mkdir -p '/etc/swanctl/pubkey'   # Raw public keys
-      mkdir -p '/etc/swanctl/private'  # Private keys in any format
-      mkdir -p '/etc/swanctl/rsa'      # PKCS#1 encoded RSA private keys
-      mkdir -p '/etc/swanctl/ecdsa'    # Plain ECDSA private keys
-      mkdir -p '/etc/swanctl/bliss'
-      mkdir -p '/etc/swanctl/pkcs8'    # PKCS#8 encoded private keys of any type
-      mkdir -p '/etc/swanctl/pkcs12'   # PKCS#12 containers
-    '';
+    systemd.tmpfiles.rules = [
+      "d /etc/swanctl/x509 -"     # Trusted X.509 end entity certificates
+      "d /etc/swanctl/x509ca -"   # Trusted X.509 Certificate Authority certificates
+      "d /etc/swanctl/x509ocsp -"
+      "d /etc/swanctl/x509aa -"   # Trusted X.509 Attribute Authority certificates
+      "d /etc/swanctl/x509ac -"   # Attribute Certificates
+      "d /etc/swanctl/x509crl -"  # Certificate Revocation Lists
+      "d /etc/swanctl/pubkey -"   # Raw public keys
+      "d /etc/swanctl/private -"  # Private keys in any format
+      "d /etc/swanctl/rsa -"      # PKCS#1 encoded RSA private keys
+      "d /etc/swanctl/ecdsa -"    # Plain ECDSA private keys
+      "d /etc/swanctl/bliss -"
+      "d /etc/swanctl/pkcs8 -"    # PKCS#8 encoded private keys of any type
+      "d /etc/swanctl/pkcs12 -"   # PKCS#12 containers
+    ];
 
     systemd.services.strongswan-swanctl = {
       description = "strongSwan IPsec IKEv1/IKEv2 daemon using swanctl";
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 346b50700c7..6d9af6141f1 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -10,6 +10,21 @@ let
   settingsFormat = pkgs.formats.json { };
   cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != {})) cfg.settings;
 
+  isUnixGui = (builtins.substring 0 1 cfg.guiAddress) == "/";
+
+  # Syncthing supports serving the GUI over Unix sockets. If that happens, the
+  # API is served over the Unix socket as well.  This function returns the correct
+  # curl arguments for the address portion of the curl command for both network
+  # and Unix socket addresses.
+  curlAddressArgs = path: if isUnixGui
+    # if cfg.guiAddress is a unix socket, tell curl explicitly about it
+    # note that the dot in front of `${path}` is the hostname, which is
+    # required.
+    then "--unix-socket ${cfg.guiAddress} http://.${path}"
+    # no adjustements are needed if cfg.guiAddress is a network address
+    else "${cfg.guiAddress}${path}"
+    ;
+
   devices = mapAttrsToList (_: device: device // {
     deviceID = device.id;
   }) cfg.settings.devices;
@@ -36,17 +51,15 @@ let
     # be careful not to leak secrets in the filesystem or in process listings
     umask 0077
 
-    # get the api key by parsing the config.xml
-    while
-        ! ${pkgs.libxml2}/bin/xmllint \
-            --xpath 'string(configuration/gui/apikey)' \
-            ${cfg.configDir}/config.xml \
-            >"$RUNTIME_DIRECTORY/api_key"
-    do sleep 1; done
-
-    (printf "X-API-Key: "; cat "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
-
     curl() {
+        # get the api key by parsing the config.xml
+        while
+            ! ${pkgs.libxml2}/bin/xmllint \
+                --xpath 'string(configuration/gui/apikey)' \
+                ${cfg.configDir}/config.xml \
+                >"$RUNTIME_DIRECTORY/api_key"
+        do sleep 1; done
+        (printf "X-API-Key: "; cat "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
         ${pkgs.curl}/bin/curl -sSLk -H "@$RUNTIME_DIRECTORY/headers" \
             --retry 1000 --retry-delay 1 --retry-all-errors \
             "$@"
@@ -64,14 +77,14 @@ let
       GET_IdAttrName = "deviceID";
       override = cfg.overrideDevices;
       conf = devices;
-      baseAddress = "${cfg.guiAddress}/rest/config/devices";
+      baseAddress = curlAddressArgs "/rest/config/devices";
     };
     dirs = {
       new_conf_IDs = map (v: v.id) folders;
       GET_IdAttrName = "id";
       override = cfg.overrideFolders;
       conf = folders;
-      baseAddress = "${cfg.guiAddress}/rest/config/folders";
+      baseAddress = curlAddressArgs "/rest/config/folders";
     };
   } [
     # Now for each of these attributes, write the curl commands that are
@@ -100,13 +113,13 @@ let
       the Nix configured list of IDs
       */
       + lib.optionalString s.override ''
-        old_conf_${conf_type}_ids="$(curl -X GET ${s.baseAddress} | ${jq} --raw-output '.[].${s.GET_IdAttrName}')"
-        for id in ''${old_conf_${conf_type}_ids}; do
-          if echo ${lib.concatStringsSep " " s.new_conf_IDs} | grep -q $id; then
-            continue
-          else
-            curl -X DELETE ${s.baseAddress}/$id
-          fi
+        stale_${conf_type}_ids="$(curl -X GET ${s.baseAddress} | ${jq} \
+          --argjson new_ids ${lib.escapeShellArg (builtins.toJSON s.new_conf_IDs)} \
+          --raw-output \
+          '[.[].${s.GET_IdAttrName}] - $new_ids | .[]'
+        )"
+        for id in ''${stale_${conf_type}_ids}; do
+          curl -X DELETE ${s.baseAddress}/$id
         done
       ''
     ))
@@ -119,15 +132,14 @@ let
     builtins.attrNames
     (lib.subtractLists ["folders" "devices"])
     (map (subOption: ''
-      curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} \
-        ${cfg.guiAddress}/rest/config/${subOption}
+      curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} ${curlAddressArgs "/rest/config/${subOption}"}
     ''))
     (lib.concatStringsSep "\n")
   ]) + ''
     # restart Syncthing if required
-    if curl ${cfg.guiAddress}/rest/config/restart-required |
+    if curl ${curlAddressArgs "/rest/config/restart-required"} |
        ${jq} -e .requiresRestart > /dev/null; then
-        curl -X POST ${cfg.guiAddress}/rest/system/restart
+        curl -X POST ${curlAddressArgs "/rest/system/restart"}
     fi
   '');
 in {
@@ -653,8 +665,10 @@ in {
           ExecStart = ''
             ${cfg.package}/bin/syncthing \
               -no-browser \
-              -gui-address=${cfg.guiAddress} \
-              -home=${cfg.configDir} ${escapeShellArgs cfg.extraFlags}
+              -gui-address=${if isUnixGui then "unix://" else ""}${cfg.guiAddress} \
+              -config=${cfg.configDir} \
+              -data=${cfg.dataDir} \
+              ${escapeShellArgs cfg.extraFlags}
           '';
           MemoryDenyWriteExecute = true;
           NoNewPrivileges = true;
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index 8b35cc8d666..a5d171e0baa 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -31,6 +31,12 @@ in {
 
     package = lib.mkPackageOptionMD pkgs "tailscale" {};
 
+    openFirewall = mkOption {
+      default = false;
+      type = types.bool;
+      description = lib.mdDoc "Whether to open the firewall for the specified port.";
+    };
+
     useRoutingFeatures = mkOption {
       type = types.enum [ "none" "client" "server" "both" ];
       default = "none";
@@ -113,6 +119,8 @@ in {
       "net.ipv6.conf.all.forwarding" = mkOverride 97 true;
     };
 
+    networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
     networking.firewall.checkReversePath = mkIf (cfg.useRoutingFeatures == "client" || cfg.useRoutingFeatures == "both") "loose";
 
     networking.dhcpcd.denyInterfaces = [ cfg.interfaceName ];
diff --git a/nixos/modules/services/networking/tinyproxy.nix b/nixos/modules/services/networking/tinyproxy.nix
new file mode 100644
index 00000000000..9bcd8bfd814
--- /dev/null
+++ b/nixos/modules/services/networking/tinyproxy.nix
@@ -0,0 +1,103 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.tinyproxy;
+  mkValueStringTinyproxy = with lib; v:
+        if true  ==         v then "yes"
+        else if false ==    v then "no"
+        else generators.mkValueStringDefault {} v;
+  mkKeyValueTinyproxy = {
+    mkValueString ? mkValueStringDefault {}
+  }: sep: k: v:
+    if null     ==  v then ""
+    else "${lib.strings.escape [sep] k}${sep}${mkValueString v}";
+
+  settingsFormat = (pkgs.formats.keyValue {
+      mkKeyValue = mkKeyValueTinyproxy {
+        mkValueString = mkValueStringTinyproxy;
+      } " ";
+      listsAsDuplicateKeys= true;
+  });
+  configFile = settingsFormat.generate "tinyproxy.conf" cfg.settings;
+
+in
+{
+
+  options = {
+    services.tinyproxy = {
+      enable = mkEnableOption (lib.mdDoc "Tinyproxy daemon");
+      package = mkPackageOptionMD pkgs "tinyproxy" {};
+      settings = mkOption {
+        description = lib.mdDoc "Configuration for [tinyproxy](https://tinyproxy.github.io/).";
+        default = { };
+        example = literalExpression ''{
+          Port 8888;
+          Listen 127.0.0.1;
+          Timeout 600;
+          Allow 127.0.0.1;
+          Anonymous = ['"Host"' '"Authorization"'];
+          ReversePath = '"/example/" "http://www.example.com/"';
+        }'';
+        type = types.submodule ({name, ...}: {
+          freeformType = settingsFormat.type;
+          options = {
+            Listen = mkOption {
+              type = types.str;
+              default = "127.0.0.1";
+              description = lib.mdDoc ''
+              Specify which address to listen to.
+              '';
+            };
+            Port = mkOption {
+              type = types.int;
+              default = 8888;
+              description = lib.mdDoc ''
+              Specify which port to listen to.
+              '';
+            };
+            Anonymous = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = lib.mdDoc ''
+              If an `Anonymous` keyword is present, then anonymous proxying is enabled. The headers listed with `Anonymous` are allowed through, while all others are denied. If no Anonymous keyword is present, then all headers are allowed through. You must include quotes around the headers.
+              '';
+            };
+            Filter = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = lib.mdDoc ''
+              Tinyproxy supports filtering of web sites based on URLs or domains. This option specifies the location of the file containing the filter rules, one rule per line.
+              '';
+            };
+          };
+        });
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+    systemd.services.tinyproxy = {
+      description = "TinyProxy daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = "tinyproxy";
+        Group = "tinyproxy";
+        Type = "simple";
+        ExecStart = "${getExe pkgs.tinyproxy} -d -c ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        KillSignal = "SIGINT";
+        TimeoutStopSec = "30s";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users.tinyproxy = {
+        group = "tinyproxy";
+        isSystemUser = true;
+    };
+    users.groups.tinyproxy = {};
+  };
+  meta.maintainers = with maintainers; [ tcheronneau ];
+}
diff --git a/nixos/modules/services/networking/tox-bootstrapd.nix b/nixos/modules/services/networking/tox-bootstrapd.nix
index 5c7e7a4c220..0f310a28d26 100644
--- a/nixos/modules/services/networking/tox-bootstrapd.nix
+++ b/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -47,7 +47,7 @@ in
               lib.mdDoc ''
                 Configuration for bootstrap daemon.
                 See <https://github.com/irungentoo/toxcore/blob/master/other/bootstrap_daemon/tox-bootstrapd.conf>
-                and <http://wiki.tox.im/Nodes>.
+                and <https://wiki.tox.chat/users/nodes>.
              '';
           };
       };
diff --git a/nixos/modules/services/networking/trust-dns.nix b/nixos/modules/services/networking/trust-dns.nix
index 4196d124a2a..758e33f16d3 100644
--- a/nixos/modules/services/networking/trust-dns.nix
+++ b/nixos/modules/services/networking/trust-dns.nix
@@ -54,7 +54,7 @@ in
         defaultText = "pkgs.trust-dns";
         description = mdDoc ''
           Trust-dns package to use.
-          Only `bin/trust-dns` need be provided: the other trust-dns utilities (client and resolver) are not needed.
+          The package must provide `meta.mainProgram` which names the server binary; any other utilities (client, resolver) are not needed.
         '';
       };
       quiet = mkOption {
@@ -135,7 +135,7 @@ in
           flags =  (lib.optional cfg.debug "--debug") ++ (lib.optional cfg.quiet "--quiet");
           flagsStr = builtins.concatStringsSep " " flags;
         in ''
-          ${cfg.package}/bin/trust-dns --config ${configFile} ${flagsStr}
+          ${cfg.package}/bin/${cfg.package.meta.mainProgram} --config ${configFile} ${flagsStr}
         '';
         Type = "simple";
         Restart = "on-failure";
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 0426dbb0c83..b6579af10a7 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -166,7 +166,7 @@ in {
     services.unbound.settings = {
       server = {
         directory = mkDefault cfg.stateDir;
-        username = cfg.user;
+        username = ''""'';
         chroot = ''""'';
         pidfile = ''""'';
         # when running under systemd there is no need to daemonize
@@ -245,14 +245,13 @@ in {
         NotifyAccess = "main";
         Type = "notify";
 
-        # FIXME: Which of these do we actually need, can we drop the chroot flag?
         AmbientCapabilities = [
           "CAP_NET_BIND_SERVICE"
+          "CAP_NET_RAW" # needed if ip-transparent is set to true
+        ];
+        CapabilityBoundingSet = [
+          "CAP_NET_BIND_SERVICE"
           "CAP_NET_RAW"
-          "CAP_SETGID"
-          "CAP_SETUID"
-          "CAP_SYS_CHROOT"
-          "CAP_SYS_RESOURCE"
         ];
 
         User = cfg.user;
@@ -266,22 +265,19 @@ in {
         ProtectControlGroups = true;
         ProtectKernelModules = true;
         ProtectSystem = "strict";
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelTunables = true;
         RuntimeDirectory = "unbound";
         ConfigurationDirectory = "unbound";
         StateDirectory = "unbound";
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ];
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = [
-          "~@clock"
-          "@cpu-emulation"
-          "@debug"
-          "@keyring"
-          "@module"
-          "mount"
-          "@obsolete"
-          "@resources"
-        ];
+        SystemCallFilter = [ "@system-service" ];
         RestrictNamespaces = true;
         LockPersonality = true;
         RestrictSUIDSGID = true;
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index 3579d67aa54..537a4db95ca 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -1,56 +1,61 @@
 { config, options, lib, pkgs, utils, ... }:
-with lib;
 let
   cfg = config.services.unifi;
   stateDir = "/var/lib/unifi";
-  cmd = ''
-    @${cfg.jrePackage}/bin/java java \
-        ${optionalString (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m"} \
-        ${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \
-        -jar ${stateDir}/lib/ace.jar
-  '';
+  cmd = lib.escapeShellArgs ([ "@${cfg.jrePackage}/bin/java" "java" ]
+    ++ lib.optionals (lib.versionAtLeast (lib.getVersion cfg.jrePackage) "16") [
+      "--add-opens=java.base/java.lang=ALL-UNNAMED"
+      "--add-opens=java.base/java.time=ALL-UNNAMED"
+      "--add-opens=java.base/sun.security.util=ALL-UNNAMED"
+      "--add-opens=java.base/java.io=ALL-UNNAMED"
+      "--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED"
+    ]
+    ++ (lib.optional (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m")
+    ++ (lib.optional (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m")
+    ++ cfg.extraJvmOptions
+    ++ [ "-jar" "${stateDir}/lib/ace.jar" ]);
 in
 {
 
   options = {
 
-    services.unifi.enable = mkOption {
-      type = types.bool;
+    services.unifi.enable = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = lib.mdDoc ''
         Whether or not to enable the unifi controller service.
       '';
     };
 
-    services.unifi.jrePackage = mkOption {
-      type = types.package;
-      default = if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3") then pkgs.jdk11 else pkgs.jre8;
-      defaultText = literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3" then pkgs.jdk11 else pkgs.jre8'';
+    services.unifi.jrePackage = lib.mkOption {
+      type = lib.types.package;
+      default = if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3") then pkgs.jdk11 else pkgs.jre8;
+      defaultText = lib.literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3" then pkgs.jdk11 else pkgs.jre8'';
       description = lib.mdDoc ''
         The JRE package to use. Check the release notes to ensure it is supported.
       '';
     };
 
-    services.unifi.unifiPackage = mkOption {
-      type = types.package;
+    services.unifi.unifiPackage = lib.mkOption {
+      type = lib.types.package;
       default = pkgs.unifi5;
-      defaultText = literalExpression "pkgs.unifi5";
+      defaultText = lib.literalExpression "pkgs.unifi5";
       description = lib.mdDoc ''
         The unifi package to use.
       '';
     };
 
-    services.unifi.mongodbPackage = mkOption {
-      type = types.package;
+    services.unifi.mongodbPackage = lib.mkOption {
+      type = lib.types.package;
       default = pkgs.mongodb-4_4;
-      defaultText = literalExpression "pkgs.mongodb";
+      defaultText = lib.literalExpression "pkgs.mongodb";
       description = lib.mdDoc ''
         The mongodb package to use. Please note: unifi7 officially only supports mongodb up until 3.6 but works with 4.4.
       '';
     };
 
-    services.unifi.openFirewall = mkOption {
-      type = types.bool;
+    services.unifi.openFirewall = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = lib.mdDoc ''
         Whether or not to open the minimum required ports on the firewall.
@@ -61,8 +66,8 @@ in
       '';
     };
 
-    services.unifi.initialJavaHeapSize = mkOption {
-      type = types.nullOr types.int;
+    services.unifi.initialJavaHeapSize = lib.mkOption {
+      type = with lib.types; nullOr int;
       default = null;
       example = 1024;
       description = lib.mdDoc ''
@@ -71,8 +76,8 @@ in
       '';
     };
 
-    services.unifi.maximumJavaHeapSize = mkOption {
-      type = types.nullOr types.int;
+    services.unifi.maximumJavaHeapSize = lib.mkOption {
+      type = with lib.types; nullOr int;
       default = null;
       example = 4096;
       description = lib.mdDoc ''
@@ -81,9 +86,18 @@ in
       '';
     };
 
+    services.unifi.extraJvmOptions = lib.mkOption {
+      type = with lib.types; listOf str;
+      default = [ ];
+      example = lib.literalExpression ''["-Xlog:gc"]'';
+      description = lib.mdDoc ''
+        Set extra options to pass to the JVM.
+      '';
+    };
+
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
 
     users.users.unifi = {
       isSystemUser = true;
@@ -93,7 +107,7 @@ in
     };
     users.groups.unifi = {};
 
-    networking.firewall = mkIf cfg.openFirewall {
+    networking.firewall = lib.mkIf cfg.openFirewall {
       # https://help.ubnt.com/hc/en-us/articles/218506997
       allowedTCPPorts = [
         8080  # Port for UAP to inform controller.
@@ -119,8 +133,8 @@ in
 
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${(removeSuffix "\n" cmd)} start";
-        ExecStop = "${(removeSuffix "\n" cmd)} stop";
+        ExecStart = "${cmd} start";
+        ExecStop = "${cmd} stop";
         Restart = "on-failure";
         TimeoutSec = "5min";
         User = "unifi";
@@ -162,7 +176,7 @@ in
         StateDirectory = "unifi";
         RuntimeDirectory = "unifi";
         LogsDirectory = "unifi";
-        CacheDirectory= "unifi";
+        CacheDirectory = "unifi";
 
         TemporaryFileSystem = [
           # required as we want to create bind mounts below
@@ -172,7 +186,7 @@ in
         # We must create the binary directories as bind mounts instead of symlinks
         # This is because the controller resolves all symlinks to absolute paths
         # to be used as the working directory.
-        BindPaths =  [
+        BindPaths = [
           "/var/log/unifi:${stateDir}/logs"
           "/run/unifi:${stateDir}/run"
           "${cfg.unifiPackage}/dl:${stateDir}/dl"
@@ -190,7 +204,7 @@ in
 
   };
   imports = [
-    (mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data" )
-    (mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ])
+    (lib.mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data")
+    (lib.mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ])
   ];
 }
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 21473388d76..d36be87daf6 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -164,6 +164,15 @@ let
           of the wireguard network has to be adjusted as well.
         '';
       };
+
+      metric = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 700;
+        description = lib.mdDoc ''
+          Set the metric of routes related to this Wireguard interface.
+        '';
+      };
     };
 
   };
@@ -395,7 +404,7 @@ let
             optionalString interfaceCfg.allowedIPsAsRoutes
               (concatMapStringsSep "\n"
                 (allowedIP:
-                  ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"''
+                  ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}" ${optionalString (interfaceCfg.metric != null) "metric ${toString interfaceCfg.metric}"}''
                 ) peer.allowedIPs);
         in ''
           ${wg_setup}
@@ -577,6 +586,7 @@ in
         }) all_peers;
 
     boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
+    boot.kernelModules = [ "wireguard" ];
     environment.systemPackages = [ pkgs.wireguard-tools ];
 
     systemd.services =
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 0595e9e6df2..90d9c68433c 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -530,5 +530,5 @@ in {
     '';
   };
 
-  meta.maintainers = with lib.maintainers; [ globin rnhmjoj ];
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
 }
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index 55a6002d61a..56d81fb0401 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -116,11 +116,18 @@ in
       };
 
       persistentKeys = mkEnableOption (lib.mdDoc ''
-        If enabled then keys will be generated once and Yggdrasil
+        persistent keys. If enabled then keys will be generated once and Yggdrasil
         will retain the same IPv6 address when the service is
-        restarted. Keys are stored at ${keysPath}.
+        restarted. Keys are stored at ${keysPath}
       '');
 
+      extraArgs = mkOption {
+        type = listOf str;
+        default = [ ];
+        example = [ "-loglevel" "info" ];
+        description = lib.mdDoc "Extra command line arguments.";
+      };
+
     };
   };
 
@@ -181,7 +188,7 @@ in
             "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"}
 
           # start yggdrasil
-          ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf
+          ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf ${lib.strings.escapeShellArgs cfg.extraArgs}
         '';
 
         serviceConfig = {
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 279b26bb895..3a274430347 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -108,6 +108,12 @@ let
   containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0;
   getGutenprint = pkgs: head (filterGutenprint pkgs);
 
+  parsePorts = addresses: let
+    splitAddress = addr: strings.splitString ":" addr;
+    extractPort = addr: builtins.foldl' (a: b: b) "" (splitAddress addr);
+  in
+    builtins.map (address: strings.toInt (extractPort address)) addresses;
+
 in
 
 {
@@ -172,6 +178,15 @@ in
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open the firewall for TCP/UDP ports specified in
+          listenAdrresses option.
+        '';
+      };
+
       bindirCmds = mkOption {
         type = types.lines;
         internal = true;
@@ -463,6 +478,13 @@ in
 
     security.pam.services.cups = {};
 
+    networking.firewall = let
+      listenPorts = parsePorts cfg.listenAddresses;
+    in mkIf cfg.openFirewall {
+      allowedTCPPorts = listenPorts;
+      allowedUDPPorts = listenPorts;
+    };
+
   };
 
   meta.maintainers = with lib.maintainers; [ matthewbauer ];
diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix
deleted file mode 100644
index a5e132d5c38..00000000000
--- a/nixos/modules/services/search/kibana.nix
+++ /dev/null
@@ -1,213 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.kibana;
-  opt = options.services.kibana;
-
-  ge7 = builtins.compareVersions cfg.package.version "7" >= 0;
-  lt6_6 = builtins.compareVersions cfg.package.version "6.6" < 0;
-
-  cfgFile = pkgs.writeText "kibana.json" (builtins.toJSON (
-    (filterAttrsRecursive (n: v: v != null && v != []) ({
-      server.host = cfg.listenAddress;
-      server.port = cfg.port;
-      server.ssl.certificate = cfg.cert;
-      server.ssl.key = cfg.key;
-
-      kibana.index = cfg.index;
-      kibana.defaultAppId = cfg.defaultAppId;
-
-      elasticsearch.url = cfg.elasticsearch.url;
-      elasticsearch.hosts = cfg.elasticsearch.hosts;
-      elasticsearch.username = cfg.elasticsearch.username;
-      elasticsearch.password = cfg.elasticsearch.password;
-
-      elasticsearch.ssl.certificate = cfg.elasticsearch.cert;
-      elasticsearch.ssl.key = cfg.elasticsearch.key;
-      elasticsearch.ssl.certificateAuthorities = cfg.elasticsearch.certificateAuthorities;
-    } // cfg.extraConf)
-  )));
-
-in {
-  options.services.kibana = {
-    enable = mkEnableOption (lib.mdDoc "kibana service");
-
-    listenAddress = mkOption {
-      description = lib.mdDoc "Kibana listening host";
-      default = "127.0.0.1";
-      type = types.str;
-    };
-
-    port = mkOption {
-      description = lib.mdDoc "Kibana listening port";
-      default = 5601;
-      type = types.port;
-    };
-
-    cert = mkOption {
-      description = lib.mdDoc "Kibana ssl certificate.";
-      default = null;
-      type = types.nullOr types.path;
-    };
-
-    key = mkOption {
-      description = lib.mdDoc "Kibana ssl key.";
-      default = null;
-      type = types.nullOr types.path;
-    };
-
-    index = mkOption {
-      description = lib.mdDoc "Elasticsearch index to use for saving kibana config.";
-      default = ".kibana";
-      type = types.str;
-    };
-
-    defaultAppId = mkOption {
-      description = lib.mdDoc "Elasticsearch default application id.";
-      default = "discover";
-      type = types.str;
-    };
-
-    elasticsearch = {
-      url = mkOption {
-        description = lib.mdDoc ''
-          Elasticsearch url.
-
-          Defaults to `"http://localhost:9200"`.
-
-          Don't set this when using Kibana >= 7.0.0 because it will result in a
-          configuration error. Use {option}`services.kibana.elasticsearch.hosts`
-          instead.
-        '';
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      hosts = mkOption {
-        description = lib.mdDoc ''
-          The URLs of the Elasticsearch instances to use for all your queries.
-          All nodes listed here must be on the same cluster.
-
-          Defaults to `[ "http://localhost:9200" ]`.
-
-          This option is only valid when using kibana >= 6.6.
-        '';
-        default = null;
-        type = types.nullOr (types.listOf types.str);
-      };
-
-      username = mkOption {
-        description = lib.mdDoc "Username for elasticsearch basic auth.";
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      password = mkOption {
-        description = lib.mdDoc "Password for elasticsearch basic auth.";
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      ca = mkOption {
-        description = lib.mdDoc ''
-          CA file to auth against elasticsearch.
-
-          It's recommended to use the {option}`certificateAuthorities` option
-          when using kibana-5.4 or newer.
-        '';
-        default = null;
-        type = types.nullOr types.path;
-      };
-
-      certificateAuthorities = mkOption {
-        description = lib.mdDoc ''
-          CA files to auth against elasticsearch.
-
-          Please use the {option}`ca` option when using kibana \< 5.4
-          because those old versions don't support setting multiple CA's.
-
-          This defaults to the singleton list [ca] when the {option}`ca` option is defined.
-        '';
-        default = lib.optional (cfg.elasticsearch.ca != null) ca;
-        defaultText = literalExpression ''
-          lib.optional (config.${opt.elasticsearch.ca} != null) ca
-        '';
-        type = types.listOf types.path;
-      };
-
-      cert = mkOption {
-        description = lib.mdDoc "Certificate file to auth against elasticsearch.";
-        default = null;
-        type = types.nullOr types.path;
-      };
-
-      key = mkOption {
-        description = lib.mdDoc "Key file to auth against elasticsearch.";
-        default = null;
-        type = types.nullOr types.path;
-      };
-    };
-
-    package = mkOption {
-      description = lib.mdDoc "Kibana package to use";
-      default = pkgs.kibana;
-      defaultText = literalExpression "pkgs.kibana";
-      type = types.package;
-    };
-
-    dataDir = mkOption {
-      description = lib.mdDoc "Kibana data directory";
-      default = "/var/lib/kibana";
-      type = types.path;
-    };
-
-    extraConf = mkOption {
-      description = lib.mdDoc "Kibana extra configuration";
-      default = {};
-      type = types.attrs;
-    };
-  };
-
-  config = mkIf (cfg.enable) {
-    assertions = [
-      {
-        assertion = ge7 -> cfg.elasticsearch.url == null;
-        message =
-          "The option services.kibana.elasticsearch.url has been removed when using kibana >= 7.0.0. " +
-          "Please use option services.kibana.elasticsearch.hosts instead.";
-      }
-      {
-        assertion = lt6_6 -> cfg.elasticsearch.hosts == null;
-        message =
-          "The option services.kibana.elasticsearch.hosts is only valid for kibana >= 6.6.";
-      }
-    ];
-    systemd.services.kibana = {
-      description = "Kibana Service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "elasticsearch.service" ];
-      environment = { BABEL_CACHE_PATH = "${cfg.dataDir}/.babelcache.json"; };
-      serviceConfig = {
-        ExecStart =
-          "${cfg.package}/bin/kibana" +
-          " --config ${cfgFile}" +
-          " --path.data ${cfg.dataDir}";
-        User = "kibana";
-        WorkingDirectory = cfg.dataDir;
-      };
-    };
-
-    environment.systemPackages = [ cfg.package ];
-
-    users.users.kibana = {
-      isSystemUser = true;
-      description = "Kibana service user";
-      home = cfg.dataDir;
-      createHome = true;
-      group = "kibana";
-    };
-    users.groups.kibana = {};
-  };
-}
diff --git a/nixos/modules/services/search/opensearch.nix b/nixos/modules/services/search/opensearch.nix
index 9a50e796313..ae79d5545fd 100644
--- a/nixos/modules/services/search/opensearch.nix
+++ b/nixos/modules/services/search/opensearch.nix
@@ -72,6 +72,18 @@ in
             The port to listen on for transport traffic.
           '';
         };
+
+        options."plugins.security.disabled" = lib.mkOption {
+          type = lib.types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to enable the security plugin,
+            `plugins.security.ssl.transport.keystore_filepath` or
+            `plugins.security.ssl.transport.server.pemcert_filepath` and
+            `plugins.security.ssl.transport.client.pemcert_filepath`
+            must be set for this plugin to be enabled.
+          '';
+        };
       };
 
       default = {};
@@ -186,6 +198,13 @@ in
               shopt -s inherit_errexit
 
               # Install plugins
+
+              # remove plugins directory if it is empty.
+              if [ -z "$(ls -A ${cfg.dataDir}/plugins)" ]; then
+                rm -r "${cfg.dataDir}/plugins"
+              fi
+
+              ln -sfT "${cfg.package}/plugins" "${cfg.dataDir}/plugins"
               ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
               ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
 
diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix
index 9393fa75128..235f29ab8a6 100644
--- a/nixos/modules/services/security/fail2ban.nix
+++ b/nixos/modules/services/security/fail2ban.nix
@@ -103,9 +103,9 @@ in
       };
 
       bantime = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        example = "10m";
+        default = "10m";
+        type = types.str;
+        example = "1h";
         description = lib.mdDoc "Number of seconds that a host is banned.";
       };
 
@@ -393,7 +393,7 @@ in
           )
         ) // {
           # Miscellaneous options
-          inherit (cfg) banaction maxretry;
+          inherit (cfg) banaction maxretry bantime;
           ignoreip = ''127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}'';
           backend = "systemd";
           # Actions
diff --git a/nixos/modules/services/security/hockeypuck.nix b/nixos/modules/services/security/hockeypuck.nix
index 127134bc5db..56c13d79192 100644
--- a/nixos/modules/services/security/hockeypuck.nix
+++ b/nixos/modules/services/security/hockeypuck.nix
@@ -55,7 +55,7 @@ in {
             ensureDatabases = [ "hockeypuck" ];
             ensureUsers = [{
               name = "hockeypuck";
-              ensurePermissions."DATABASE hockeypuck" = "ALL PRIVILEGES";
+              ensureDBOwnership = true;
             }];
           };
         ```
diff --git a/nixos/modules/services/security/jitterentropy-rngd.nix b/nixos/modules/services/security/jitterentropy-rngd.nix
new file mode 100644
index 00000000000..7bfacb5ddc5
--- /dev/null
+++ b/nixos/modules/services/security/jitterentropy-rngd.nix
@@ -0,0 +1,18 @@
+{ lib, config, pkgs, ... }:
+let
+  cfg = config.services.jitterentropy-rngd;
+in
+{
+  options.services.jitterentropy-rngd = {
+    enable =
+      lib.mkEnableOption (lib.mdDoc "jitterentropy-rngd service configuration");
+    package = lib.mkPackageOptionMD pkgs "jitterentropy-rngd" { };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [ cfg.package ];
+    systemd.services."jitterentropy".wantedBy = [ "basic.target" ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ thillux ];
+}
diff --git a/nixos/modules/services/security/opensnitch.nix b/nixos/modules/services/security/opensnitch.nix
index 013aeb16756..97ac3a72804 100644
--- a/nixos/modules/services/security/opensnitch.nix
+++ b/nixos/modules/services/security/opensnitch.nix
@@ -172,7 +172,7 @@ in {
         ln -sf '${file}' "${local}"
       '') rules}
 
-      if [ ! -f /etc/opensnitch-system-fw.json ]; then
+      if [ ! -f /etc/opensnitchd/system-fw.json ]; then
         cp "${pkgs.opensnitch}/etc/opensnitchd/system-fw.json" "/etc/opensnitchd/system-fw.json"
       fi
     '');
diff --git a/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix
deleted file mode 100644
index 664335cb58e..00000000000
--- a/nixos/modules/services/security/privacyidea.nix
+++ /dev/null
@@ -1,458 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.privacyidea;
-  opt = options.services.privacyidea;
-
-  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python310; };
-  python = uwsgi.python3;
-  penv = python.withPackages (const [ pkgs.privacyidea ]);
-  logCfg = pkgs.writeText "privacyidea-log.cfg" ''
-    [formatters]
-    keys=detail
-
-    [handlers]
-    keys=stream
-
-    [formatter_detail]
-    class=privacyidea.lib.log.SecureFormatter
-    format=[%(asctime)s][%(process)d][%(thread)d][%(levelname)s][%(name)s:%(lineno)d] %(message)s
-
-    [handler_stream]
-    class=StreamHandler
-    level=NOTSET
-    formatter=detail
-    args=(sys.stdout,)
-
-    [loggers]
-    keys=root,privacyidea
-
-    [logger_privacyidea]
-    handlers=stream
-    qualname=privacyidea
-    level=INFO
-
-    [logger_root]
-    handlers=stream
-    level=ERROR
-  '';
-
-  piCfgFile = pkgs.writeText "privacyidea.cfg" ''
-    SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
-    SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2:///privacyidea'
-    SECRET_KEY = '${cfg.secretKey}'
-    PI_PEPPER = '${cfg.pepper}'
-    PI_ENCFILE = '${cfg.encFile}'
-    PI_AUDIT_KEY_PRIVATE = '${cfg.auditKeyPrivate}'
-    PI_AUDIT_KEY_PUBLIC = '${cfg.auditKeyPublic}'
-    PI_LOGCONFIG = '${logCfg}'
-    ${cfg.extraConfig}
-  '';
-
-  renderValue = x:
-    if isList x then concatMapStringsSep "," (x: ''"${x}"'') x
-    else if isString x && hasInfix "," x then ''"${x}"''
-    else x;
-
-  ldapProxyConfig = pkgs.writeText "ldap-proxy.ini"
-    (generators.toINI {}
-      (flip mapAttrs cfg.ldap-proxy.settings
-        (const (mapAttrs (const renderValue)))));
-
-  privacyidea-token-janitor = pkgs.writeShellScriptBin "privacyidea-token-janitor" ''
-    exec -a privacyidea-token-janitor \
-      /run/wrappers/bin/sudo -u ${cfg.user} \
-      env PRIVACYIDEA_CONFIGFILE=${cfg.stateDir}/privacyidea.cfg \
-      ${penv}/bin/privacyidea-token-janitor $@
-  '';
-in
-
-{
-  options = {
-    services.privacyidea = {
-      enable = mkEnableOption (lib.mdDoc "PrivacyIDEA");
-
-      environmentFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/root/privacyidea.env";
-        description = lib.mdDoc ''
-          File to load as environment file. Environment variables
-          from this file will be interpolated into the config file
-          using `envsubst` which is helpful for specifying
-          secrets:
-          ```
-          { services.privacyidea.secretKey = "$SECRET"; }
-          ```
-
-          The environment-file can now specify the actual secret key:
-          ```
-          SECRET=veryverytopsecret
-          ```
-        '';
-      };
-
-      stateDir = mkOption {
-        type = types.str;
-        default = "/var/lib/privacyidea";
-        description = lib.mdDoc ''
-          Directory where all PrivacyIDEA files will be placed by default.
-        '';
-      };
-
-      superuserRealm = mkOption {
-        type = types.listOf types.str;
-        default = [ "super" "administrators" ];
-        description = lib.mdDoc ''
-          The realm where users are allowed to login as administrators.
-        '';
-      };
-
-      secretKey = mkOption {
-        type = types.str;
-        example = "t0p s3cr3t";
-        description = lib.mdDoc ''
-          This is used to encrypt the auth_token.
-        '';
-      };
-
-      pepper = mkOption {
-        type = types.str;
-        example = "Never know...";
-        description = lib.mdDoc ''
-          This is used to encrypt the admin passwords.
-        '';
-      };
-
-      encFile = mkOption {
-        type = types.str;
-        default = "${cfg.stateDir}/enckey";
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/enckey"'';
-        description = lib.mdDoc ''
-          This is used to encrypt the token data and token passwords
-        '';
-      };
-
-      auditKeyPrivate = mkOption {
-        type = types.str;
-        default = "${cfg.stateDir}/private.pem";
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/private.pem"'';
-        description = lib.mdDoc ''
-          Private Key for signing the audit log.
-        '';
-      };
-
-      auditKeyPublic = mkOption {
-        type = types.str;
-        default = "${cfg.stateDir}/public.pem";
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/public.pem"'';
-        description = lib.mdDoc ''
-          Public key for checking signatures of the audit log.
-        '';
-      };
-
-      adminPasswordFile = mkOption {
-        type = types.path;
-        description = lib.mdDoc "File containing password for the admin user";
-      };
-
-      adminEmail = mkOption {
-        type = types.str;
-        example = "admin@example.com";
-        description = lib.mdDoc "Mail address for the admin user";
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = lib.mdDoc ''
-          Extra configuration options for pi.cfg.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "privacyidea";
-        description = lib.mdDoc "User account under which PrivacyIDEA runs.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "privacyidea";
-        description = lib.mdDoc "Group account under which PrivacyIDEA runs.";
-      };
-
-      tokenjanitor = {
-        enable = mkEnableOption (lib.mdDoc "automatic runs of the token janitor");
-        interval = mkOption {
-          default = "quarterly";
-          type = types.str;
-          description = lib.mdDoc ''
-            Interval in which the cleanup program is supposed to run.
-            See {manpage}`systemd.time(7)` for further information.
-          '';
-        };
-        action = mkOption {
-          type = types.enum [ "delete" "mark" "disable" "unassign" ];
-          description = lib.mdDoc ''
-            Which action to take for matching tokens.
-          '';
-        };
-        unassigned = mkOption {
-          default = false;
-          type = types.bool;
-          description = lib.mdDoc ''
-            Whether to search for **unassigned** tokens
-            and apply [](#opt-services.privacyidea.tokenjanitor.action)
-            onto them.
-          '';
-        };
-        orphaned = mkOption {
-          default = true;
-          type = types.bool;
-          description = lib.mdDoc ''
-            Whether to search for **orphaned** tokens
-            and apply [](#opt-services.privacyidea.tokenjanitor.action)
-            onto them.
-          '';
-        };
-      };
-
-      ldap-proxy = {
-        enable = mkEnableOption (lib.mdDoc "PrivacyIDEA LDAP Proxy");
-
-        configFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = lib.mdDoc ''
-            Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
-          '';
-        };
-
-        user = mkOption {
-          type = types.str;
-          default = "pi-ldap-proxy";
-          description = lib.mdDoc "User account under which PrivacyIDEA LDAP proxy runs.";
-        };
-
-        group = mkOption {
-          type = types.str;
-          default = "pi-ldap-proxy";
-          description = lib.mdDoc "Group account under which PrivacyIDEA LDAP proxy runs.";
-        };
-
-        settings = mkOption {
-          type = with types; attrsOf (attrsOf (oneOf [ str bool int (listOf str) ]));
-          default = {};
-          description = lib.mdDoc ''
-            Attribute-set containing the settings for `privacyidea-ldap-proxy`.
-            It's possible to pass secrets using env-vars as substitutes and
-            use the option [](#opt-services.privacyidea.ldap-proxy.environmentFile)
-            to inject them via `envsubst`.
-          '';
-        };
-
-        environmentFile = mkOption {
-          default = null;
-          type = types.nullOr types.str;
-          description = lib.mdDoc ''
-            Environment file containing secrets to be substituted into
-            [](#opt-services.privacyidea.ldap-proxy.settings).
-          '';
-        };
-      };
-    };
-  };
-
-  config = mkMerge [
-
-    (mkIf cfg.enable {
-
-      assertions = [
-        {
-          assertion = cfg.tokenjanitor.enable -> (cfg.tokenjanitor.orphaned || cfg.tokenjanitor.unassigned);
-          message = ''
-            privacyidea-token-janitor has no effect if neither orphaned nor unassigned tokens
-            are to be searched.
-          '';
-        }
-      ];
-
-      environment.systemPackages = [ pkgs.privacyidea (hiPrio privacyidea-token-janitor) ];
-
-      services.postgresql.enable = mkDefault true;
-
-      systemd.services.privacyidea-tokenjanitor = mkIf cfg.tokenjanitor.enable {
-        environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
-        path = [ penv ];
-        serviceConfig = {
-          CapabilityBoundingSet = [ "" ];
-          ExecStart = "${pkgs.writeShellScript "pi-token-janitor" ''
-            ${optionalString cfg.tokenjanitor.orphaned ''
-              echo >&2 "Removing orphaned tokens..."
-              privacyidea-token-janitor find \
-                --orphaned true \
-                --action ${cfg.tokenjanitor.action}
-            ''}
-            ${optionalString cfg.tokenjanitor.unassigned ''
-              echo >&2 "Removing unassigned tokens..."
-              privacyidea-token-janitor find \
-                --assigned false \
-                --action ${cfg.tokenjanitor.action}
-            ''}
-          ''}";
-          Group = cfg.group;
-          LockPersonality = true;
-          MemoryDenyWriteExecute = true;
-          ProtectHome = true;
-          ProtectHostname = true;
-          ProtectKernelLogs = true;
-          ProtectKernelModules = true;
-          ProtectKernelTunables = true;
-          ProtectSystem = "strict";
-          ReadWritePaths = cfg.stateDir;
-          Type = "oneshot";
-          User = cfg.user;
-          WorkingDirectory = cfg.stateDir;
-        };
-      };
-      systemd.timers.privacyidea-tokenjanitor = mkIf cfg.tokenjanitor.enable {
-        wantedBy = [ "timers.target" ];
-        timerConfig.OnCalendar = cfg.tokenjanitor.interval;
-        timerConfig.Persistent = true;
-      };
-
-      systemd.services.privacyidea = let
-        piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON {
-          uwsgi = {
-            buffer-size = 8192;
-            plugins = [ "python3" ];
-            pythonpath = "${penv}/${uwsgi.python3.sitePackages}";
-            socket = "/run/privacyidea/socket";
-            uid = cfg.user;
-            gid = cfg.group;
-            chmod-socket = 770;
-            chown-socket = "${cfg.user}:nginx";
-            chdir = cfg.stateDir;
-            wsgi-file = "${penv}/etc/privacyidea/privacyideaapp.wsgi";
-            processes = 4;
-            harakiri = 60;
-            reload-mercy = 8;
-            stats = "/run/privacyidea/stats.socket";
-            max-requests = 2000;
-            limit-as = 1024;
-            reload-on-as = 512;
-            reload-on-rss = 256;
-            no-orphans = true;
-            vacuum = true;
-          };
-        });
-      in {
-        wantedBy = [ "multi-user.target" ];
-        after = [ "postgresql.service" ];
-        path = with pkgs; [ openssl ];
-        environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
-        preStart = let
-          pi-manage = "${config.security.sudo.package}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
-          pgsu = config.services.postgresql.superUser;
-          psql = config.services.postgresql.package;
-        in ''
-          mkdir -p ${cfg.stateDir} /run/privacyidea
-          chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea
-          umask 077
-          ${lib.getBin pkgs.envsubst}/bin/envsubst -o ${cfg.stateDir}/privacyidea.cfg \
-                                                   -i "${piCfgFile}"
-          chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/privacyidea.cfg
-          if ! test -e "${cfg.stateDir}/db-created"; then
-            ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
-            ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
-            ${pi-manage} create_enckey
-            ${pi-manage} create_audit_keys
-            ${pi-manage} createdb
-            ${pi-manage} admin add admin -e ${cfg.adminEmail} -p "$(cat ${cfg.adminPasswordFile})"
-            ${pi-manage} db stamp head -d ${penv}/lib/privacyidea/migrations
-            touch "${cfg.stateDir}/db-created"
-            chmod g+r "${cfg.stateDir}/enckey" "${cfg.stateDir}/private.pem"
-          fi
-          ${pi-manage} db upgrade -d ${penv}/lib/privacyidea/migrations
-        '';
-        serviceConfig = {
-          Type = "notify";
-          ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}";
-          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
-          ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
-          NotifyAccess = "main";
-          KillSignal = "SIGQUIT";
-        };
-      };
-
-      users.users.privacyidea = mkIf (cfg.user == "privacyidea") {
-        group = cfg.group;
-        isSystemUser = true;
-      };
-
-      users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {};
-    })
-
-    (mkIf cfg.ldap-proxy.enable {
-
-      assertions = [
-        { assertion = let
-            xor = a: b: a && !b || !a && b;
-          in xor (cfg.ldap-proxy.settings == {}) (cfg.ldap-proxy.configFile == null);
-          message = "configFile & settings are mutually exclusive for services.privacyidea.ldap-proxy!";
-        }
-      ];
-
-      warnings = mkIf (cfg.ldap-proxy.configFile != null) [
-        "Using services.privacyidea.ldap-proxy.configFile is deprecated! Use the RFC42-style settings option instead!"
-      ];
-
-      systemd.services.privacyidea-ldap-proxy = let
-        ldap-proxy-env = pkgs.python3.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
-      in {
-        description = "privacyIDEA LDAP proxy";
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          User = cfg.ldap-proxy.user;
-          Group = cfg.ldap-proxy.group;
-          StateDirectory = "privacyidea-ldap-proxy";
-          EnvironmentFile = mkIf (cfg.ldap-proxy.environmentFile != null)
-            [ cfg.ldap-proxy.environmentFile ];
-          ExecStartPre =
-            "${pkgs.writeShellScript "substitute-secrets-ldap-proxy" ''
-              umask 0077
-              ${pkgs.envsubst}/bin/envsubst \
-                -i ${ldapProxyConfig} \
-                -o $STATE_DIRECTORY/ldap-proxy.ini
-            ''}";
-          ExecStart = let
-            configPath = if cfg.ldap-proxy.settings != {}
-              then "%S/privacyidea-ldap-proxy/ldap-proxy.ini"
-              else cfg.ldap-proxy.configFile;
-          in ''
-            ${ldap-proxy-env}/bin/twistd \
-              --nodaemon \
-              --pidfile= \
-              -u ${cfg.ldap-proxy.user} \
-              -g ${cfg.ldap-proxy.group} \
-              ldap-proxy \
-              -c ${configPath}
-          '';
-          Restart = "always";
-        };
-      };
-
-      users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") {
-        group = cfg.ldap-proxy.group;
-        isSystemUser = true;
-      };
-
-      users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {};
-    })
-  ];
-
-}
diff --git a/nixos/modules/services/security/tang.nix b/nixos/modules/services/security/tang.nix
new file mode 100644
index 00000000000..9cb0a22fca4
--- /dev/null
+++ b/nixos/modules/services/security/tang.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.tang;
+in
+{
+  options.services.tang = {
+    enable = mkEnableOption "tang";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.tang;
+      defaultText = literalExpression "pkgs.tang";
+      description = mdDoc "The tang package to use.";
+    };
+
+    listenStream = mkOption {
+      type = with types; listOf str;
+      default = [ "7654" ];
+      example = [ "198.168.100.1:7654" "[2001:db8::1]:7654" "7654" ];
+      description = mdDoc ''
+        Addresses and/or ports on which tang should listen.
+        For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
+      '';
+    };
+
+    ipAddressAllow = mkOption {
+      example = [ "192.168.1.0/24" ];
+      type = types.listOf types.str;
+      description = ''
+        Whitelist a list of address prefixes.
+        Preferably, internal addresses should be used.
+      '';
+    };
+
+  };
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services."tangd@" = {
+      description = "Tang server";
+      path = [ cfg.package ];
+      serviceConfig = {
+        StandardInput = "socket";
+        StandardOutput = "socket";
+        StandardError = "journal";
+        DynamicUser = true;
+        StateDirectory = "tang";
+        RuntimeDirectory = "tang";
+        StateDirectoryMode = "700";
+        UMask = "0077";
+        CapabilityBoundingSet = [ "" ];
+        ExecStart = "${cfg.package}/libexec/tangd %S/tang";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        DeviceAllow = [ "/dev/stdin" ];
+        RestrictAddressFamilies = [ "AF_UNIX" ];
+        DevicePolicy = "strict";
+        PrivateDevices = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        IPAddressDeny = "any";
+        IPAddressAllow = cfg.ipAddressAllow;
+      };
+    };
+
+    systemd.sockets.tangd = {
+      description = "Tang server";
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream = cfg.listenStream;
+        Accept = "yes";
+        IPAddressDeny = "any";
+        IPAddressAllow = cfg.ipAddressAllow;
+      };
+    };
+  };
+  meta.maintainers = with lib.maintainers; [ jfroche julienmalka ];
+}
diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix
index 9b158bb9d18..071e6997514 100644
--- a/nixos/modules/services/security/usbguard.nix
+++ b/nixos/modules/services/security/usbguard.nix
@@ -7,10 +7,8 @@ let
   # valid policy options
   policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
 
-  defaultRuleFile = "/var/lib/usbguard/rules.conf";
-
   # decide what file to use for rules
-  ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else defaultRuleFile;
+  ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else cfg.ruleFile;
 
   daemonConf = ''
     # generated by nixos/modules/services/security/usbguard.nix
@@ -51,6 +49,19 @@ in
         '';
       };
 
+      ruleFile = mkOption {
+        type = types.nullOr types.path;
+        default = "/var/lib/usbguard/rules.conf";
+        example = "/run/secrets/usbguard-rules";
+        description = lib.mdDoc ''
+          This tells the USBGuard daemon which file to load as policy rule set.
+
+          The file can be changed manually or via the IPC interface assuming it has the right file permissions.
+
+          For more details see {manpage}`usbguard-rules.conf(5)`.
+        '';
+
+      };
       rules = mkOption {
         type = types.nullOr types.lines;
         default = null;
@@ -63,8 +74,7 @@ in
           be changed by the IPC interface.
 
           If you do not set this option, the USBGuard daemon will load
-          it's policy rule set from `${defaultRuleFile}`.
-          This file can be changed manually or via the IPC interface.
+          it's policy rule set from the option configured in `services.usbguard.ruleFile`.
 
           Running `usbguard generate-policy` as root will
           generate a config for your currently plugged in devices.
@@ -248,7 +258,6 @@ in
       '';
   };
   imports = [
-    (mkRemovedOptionModule [ "services" "usbguard" "ruleFile" ] "The usbguard module now uses ${defaultRuleFile} as ruleFile. Alternatively, use services.usbguard.rules to configure rules.")
     (mkRemovedOptionModule [ "services" "usbguard" "IPCAccessControlFiles" ] "The usbguard module now hardcodes IPCAccessControlFiles to /var/lib/usbguard/IPCAccessControl.d.")
     (mkRemovedOptionModule [ "services" "usbguard" "auditFilePath" ] "Removed usbguard module audit log files. Audit logs can be found in the systemd journal.")
     (mkRenamedOptionModule [ "services" "usbguard" "implictPolicyTarget" ] [ "services" "usbguard" "implicitPolicyTarget" ])
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index 3f501d45346..38805eba2ca 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -11,7 +11,7 @@ let
 in
 {
   options.services.earlyoom = {
-    enable = mkEnableOption (lib.mdDoc "Early out of memory killing");
+    enable = mkEnableOption (lib.mdDoc "early out of memory killing");
 
     freeMemThreshold = mkOption {
       type = types.ints.between 1 100;
diff --git a/nixos/modules/services/system/nix-daemon.nix b/nixos/modules/services/system/nix-daemon.nix
index c9df20196db..ce255cd8d0a 100644
--- a/nixos/modules/services/system/nix-daemon.nix
+++ b/nixos/modules/services/system/nix-daemon.nix
@@ -249,11 +249,6 @@ in
 
     services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers;
 
-    system.activationScripts.nix = stringAfter [ "etc" "users" ]
-      ''
-        install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user
-      '';
-
     # Legacy configuration conversion.
     nix.settings = mkMerge [
       (mkIf (isNixAtLeast "2.3pre") { sandbox-fallback = false; })
diff --git a/nixos/modules/services/system/systembus-notify.nix b/nixos/modules/services/system/systembus-notify.nix
index 269197b3997..f79879fa136 100644
--- a/nixos/modules/services/system/systembus-notify.nix
+++ b/nixos/modules/services/system/systembus-notify.nix
@@ -13,7 +13,7 @@ in
 
       WARNING: enabling this option (while convenient) should *not* be done on a
       machine where you do not trust the other users as it allows any other
-      local user to DoS your session by spamming notifications.
+      local user to DoS your session by spamming notifications
     '');
   };
 
diff --git a/nixos/modules/services/torrent/flexget.nix b/nixos/modules/services/torrent/flexget.nix
index 1b971838b32..58a4b700149 100644
--- a/nixos/modules/services/torrent/flexget.nix
+++ b/nixos/modules/services/torrent/flexget.nix
@@ -14,7 +14,7 @@ let
 in {
   options = {
     services.flexget = {
-      enable = mkEnableOption (lib.mdDoc "Run FlexGet Daemon");
+      enable = mkEnableOption (lib.mdDoc "FlexGet daemon");
 
       package = mkPackageOptionMD pkgs "flexget" {};
 
@@ -64,7 +64,6 @@ in {
         path = [ pkg ];
         serviceConfig = {
           User = cfg.user;
-          Environment = "TZ=${config.time.timeZone}";
           ExecStartPre = "${pkgs.coreutils}/bin/install -m644 ${ymlFile} ${configFile}";
           ExecStart = "${pkg}/bin/flexget -c ${configFile} daemon start";
           ExecStop = "${pkg}/bin/flexget -c ${configFile} daemon stop";
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index 752ab91fe63..5efb9334ea0 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -148,7 +148,7 @@ in
             type = types.bool;
             default = true;
             description = lib.mdDoc ''
-              Whether to enable [Micro Transport Protocol (µTP)](http://en.wikipedia.org/wiki/Micro_Transport_Protocol).
+              Whether to enable [Micro Transport Protocol (µTP)](https://en.wikipedia.org/wiki/Micro_Transport_Protocol).
             '';
           };
           options.watch-dir = mkOption {
@@ -182,7 +182,7 @@ in
         example = "770";
         description = lib.mdDoc ''
           If not `null`, is used as the permissions
-          set by `systemd.activationScripts.transmission-daemon`
+          set by `system.activationScripts.transmission-daemon`
           on the directories [](#opt-services.transmission.settings.download-dir),
           [](#opt-services.transmission.settings.incomplete-dir).
           and [](#opt-services.transmission.settings.watch-dir).
diff --git a/nixos/modules/services/video/mediamtx.nix b/nixos/modules/services/video/mediamtx.nix
index c3abd9cdcc5..50f8e881027 100644
--- a/nixos/modules/services/video/mediamtx.nix
+++ b/nixos/modules/services/video/mediamtx.nix
@@ -40,7 +40,7 @@ in
       };
 
       allowVideoAccess = lib.mkEnableOption (lib.mdDoc ''
-        Enable access to video devices like cameras on the system.
+        access to video devices like cameras on the system
       '');
     };
   };
diff --git a/nixos/modules/services/video/unifi-video.nix b/nixos/modules/services/video/unifi-video.nix
index cb438a08150..5c93f60cbd7 100644
--- a/nixos/modules/services/video/unifi-video.nix
+++ b/nixos/modules/services/video/unifi-video.nix
@@ -32,7 +32,7 @@ let
     name = "mongo.conf";
     executable = false;
     text = ''
-      # for documentation of all options, see http://docs.mongodb.org/manual/reference/configuration-options/
+      # for documentation of all options, see https://www.mongodb.com/docs/manual/reference/configuration-options/
 
       storage:
          dbPath: ${cfg.dataDir}/db
@@ -63,7 +63,7 @@ let
     executable = false;
     text = ''
       # for documentation of all options, see:
-      #   http://docs.mongodb.org/manual/reference/configuration-options/
+      #   https://www.mongodb.com/docs/manual/reference/configuration-options/
 
       storage:
          dbPath: ${cfg.dataDir}/db-wt
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix
index 8d177525861..8a8ed49115f 100644
--- a/nixos/modules/services/web-apps/akkoma.nix
+++ b/nixos/modules/services/web-apps/akkoma.nix
@@ -86,7 +86,7 @@ let
   # Erlang/Elixir uses a somewhat special format for IP addresses
   erlAddr = addr: fileContents
     (pkgs.runCommand addr {
-      nativeBuildInputs = with pkgs; [ elixir ];
+      nativeBuildInputs = [ cfg.package.elixirPackage ];
       code = ''
         case :inet.parse_address('${addr}') do
           {:ok, addr} -> IO.inspect addr
@@ -96,7 +96,7 @@ let
       passAsFile = [ "code" ];
     } ''elixir "$codePath" >"$out"'');
 
-  format = pkgs.formats.elixirConf { };
+  format = pkgs.formats.elixirConf { elixir = cfg.package.elixirPackage; };
   configFile = format.generate "config.exs"
     (replaceSec
       (attrsets.updateManyAttrsByPath [{
@@ -146,7 +146,7 @@ let
 
   initSecretsScript = writeShell {
     name = "akkoma-init-secrets";
-    runtimeInputs = with pkgs; [ coreutils elixir ];
+    runtimeInputs = with pkgs; [ coreutils cfg.package.elixirPackage ];
     text = let
       key-base = web.secret_key_base;
       jwt-signer = ex.":joken".":default_signer";
@@ -282,11 +282,11 @@ let
         AKKOMA_CONFIG_PATH="$RUNTIME_DIRECTORY/config.exs" \
         ERL_EPMD_ADDRESS="${cfg.dist.address}" \
         ERL_EPMD_PORT="${toString cfg.dist.epmdPort}" \
-        ERL_FLAGS="${concatStringsSep " " [
-          "-kernel inet_dist_use_interface '${erlAddr cfg.dist.address}'"
-          "-kernel inet_dist_listen_min ${toString cfg.dist.portMin}"
-          "-kernel inet_dist_listen_max ${toString cfg.dist.portMax}"
-        ]}" \
+        ERL_FLAGS=${lib.escapeShellArg (lib.escapeShellArgs ([
+          "-kernel" "inet_dist_use_interface" (erlAddr cfg.dist.address)
+          "-kernel" "inet_dist_listen_min" (toString cfg.dist.portMin)
+          "-kernel" "inet_dist_listen_max" (toString cfg.dist.portMax)
+        ] ++ cfg.dist.extraFlags))} \
         RELEASE_COOKIE="$(<"$RUNTIME_DIRECTORY/cookie")" \
         RELEASE_NAME="akkoma" \
           exec "${cfg.package}/bin/$(basename "$0")" "$@"
@@ -553,6 +553,13 @@ in {
           description = mdDoc "TCP port to bind Erlang Port Mapper Daemon to.";
         };
 
+        extraFlags = mkOption {
+          type = with types; listOf str;
+          default = [ ];
+          description = mdDoc "Extra flags to pass to Erlang";
+          example = [ "+sbwt" "none" "+sbwtdcpu" "none" "+sbwtdio" "none" ];
+        };
+
         portMin = mkOption {
           type = types.port;
           default = 49152;
@@ -902,7 +909,7 @@ in {
   };
 
   config = mkIf cfg.enable {
-    warnings = optionals (!config.security.sudo.enable) [''
+    warnings = optionals (with config.security; (!sudo.enable) && (!sudo-rs.enable)) [''
       The pleroma_ctl wrapper enabled by the installWrapper option relies on
       sudo, which appears to have been disabled through security.sudo.enable.
     ''];
diff --git a/nixos/modules/services/web-apps/audiobookshelf.nix b/nixos/modules/services/web-apps/audiobookshelf.nix
new file mode 100644
index 00000000000..84dffc5f9d3
--- /dev/null
+++ b/nixos/modules/services/web-apps/audiobookshelf.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.audiobookshelf;
+in
+{
+  options = {
+    services.audiobookshelf = {
+      enable = mkEnableOption "Audiobookshelf, self-hosted audiobook and podcast server.";
+
+      package = mkPackageOption pkgs "audiobookshelf" { };
+
+      dataDir = mkOption {
+        description = "Path to Audiobookshelf config and metadata inside of /var/lib.";
+        default = "audiobookshelf";
+        type = types.str;
+      };
+
+      host = mkOption {
+        description = "The host Audiobookshelf binds to.";
+        default = "127.0.0.1";
+        example = "0.0.0.0";
+        type = types.str;
+      };
+
+      port = mkOption {
+        description = "The TCP port Audiobookshelf will listen on.";
+        default = 8000;
+        type = types.port;
+      };
+
+      user = mkOption {
+        description = "User account under which Audiobookshelf runs.";
+        default = "audiobookshelf";
+        type = types.str;
+      };
+
+      group = mkOption {
+        description = "Group under which Audiobookshelf runs.";
+        default = "audiobookshelf";
+        type = types.str;
+      };
+
+      openFirewall = mkOption {
+        description = "Open ports in the firewall for the Audiobookshelf web interface.";
+        default = false;
+        type = types.bool;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.audiobookshelf = {
+      description = "Audiobookshelf is a self-hosted audiobook and podcast server";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = cfg.dataDir;
+        WorkingDirectory = "/var/lib/${cfg.dataDir}";
+        ExecStart = "${cfg.package}/bin/audiobookshelf --host ${cfg.host} --port ${toString cfg.port}";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users = mkIf (cfg.user == "audiobookshelf") {
+      audiobookshelf = {
+        isSystemUser = true;
+        group = cfg.group;
+        home = "/var/lib/${cfg.dataDir}";
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "audiobookshelf") {
+      audiobookshelf = { };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+  };
+
+  meta.maintainers = with maintainers; [ wietsedv ];
+}
diff --git a/nixos/modules/services/web-apps/c2fmzq-server.md b/nixos/modules/services/web-apps/c2fmzq-server.md
new file mode 100644
index 00000000000..236953bd4ff
--- /dev/null
+++ b/nixos/modules/services/web-apps/c2fmzq-server.md
@@ -0,0 +1,42 @@
+# c2FmZQ {#module-services-c2fmzq}
+
+c2FmZQ is an application that can securely encrypt, store, and share files,
+including but not limited to pictures and videos.
+
+The service `c2fmzq-server` can be enabled by setting
+```
+{
+  services.c2fmzq-server.enable = true;
+}
+```
+This will spin up an instance of the server which is API-compatible with
+[Stingle Photos](https://stingle.org) and an experimental Progressive Web App
+(PWA) to interact with the storage via the browser.
+
+In principle the server can be exposed directly on a public interface and there
+are command line options to manage HTTPS certificates directly, but the module
+is designed to be served behind a reverse proxy or only accessed via localhost.
+
+```
+{
+  services.c2fmzq-server = {
+    enable = true;
+    bindIP = "127.0.0.1"; # default
+    port = 8080; # default
+  };
+
+  services.nginx = {
+    enable = true;
+    recommendedProxySettings = true;
+    virtualHosts."example.com" = {
+      enableACME = true;
+      forceSSL = true;
+      locations."/" = {
+        proxyPass = "http://127.0.0.1:8080";
+      };
+    };
+  };
+}
+```
+
+For more information, see <https://github.com/c2FmZQ/c2FmZQ/>.
diff --git a/nixos/modules/services/web-apps/c2fmzq-server.nix b/nixos/modules/services/web-apps/c2fmzq-server.nix
new file mode 100644
index 00000000000..2749c2a5a87
--- /dev/null
+++ b/nixos/modules/services/web-apps/c2fmzq-server.nix
@@ -0,0 +1,125 @@
+{ lib, pkgs, config, ... }:
+
+let
+  inherit (lib) mkEnableOption mkPackageOption mkOption types;
+
+  cfg = config.services.c2fmzq-server;
+
+  argsFormat = {
+    type = with lib.types; nullOr (oneOf [ bool int str ]);
+    generate = lib.cli.toGNUCommandLineShell { };
+  };
+in {
+  options.services.c2fmzq-server = {
+    enable = mkEnableOption "c2fmzq-server";
+
+    bindIP = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = "The local address to use.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = "The local port to use.";
+    };
+
+    passphraseFile = mkOption {
+      type = types.str;
+      example = "/run/secrets/c2fmzq/pwfile";
+      description = "Path to file containing the database passphrase";
+    };
+
+    package = mkPackageOption pkgs "c2fmzq" { };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = argsFormat.type;
+
+        options = {
+          address = mkOption {
+            internal = true;
+            type = types.str;
+            default = "${cfg.bindIP}:${toString cfg.port}";
+          };
+
+          database = mkOption {
+            type = types.str;
+            default = "%S/c2fmzq-server/data";
+            description = "Path of the database";
+          };
+
+          verbose = mkOption {
+            type = types.ints.between 1 3;
+            default = 2;
+            description = "The level of logging verbosity: 1:Error 2:Info 3:Debug";
+          };
+        };
+      };
+      description = ''
+        Configuration for c2FmZQ-server passed as CLI arguments.
+        Run {command}`c2FmZQ-server help` for supported values.
+      '';
+      example = {
+        verbose = 3;
+        allow-new-accounts = true;
+        auto-approve-new-accounts = true;
+        encrypt-metadata = true;
+        enable-webapp = true;
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.c2fmzq-server = {
+      description = "c2FmZQ-server";
+      documentation = [ "https://github.com/c2FmZQ/c2FmZQ/blob/main/README.md" ];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = "${lib.getExe cfg.package} ${argsFormat.generate cfg.settings}";
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        DynamicUser = true;
+        Environment = "C2FMZQ_PASSPHRASE_FILE=%d/passphrase-file";
+        IPAccounting = true;
+        IPAddressAllow = cfg.bindIP;
+        IPAddressDeny = "any";
+        LoadCredential = "passphrase-file:${cfg.passphraseFile}";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SocketBindAllow = cfg.port;
+        SocketBindDeny = "any";
+        StateDirectory = "c2fmzq-server";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged @obsolete" ];
+      };
+    };
+  };
+
+  meta = {
+    doc = ./c2fmzq-server.md;
+    maintainers = with lib.maintainers; [ hmenke ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/cloudlog.nix b/nixos/modules/services/web-apps/cloudlog.nix
index da2cf93d7f1..5519d6967a1 100644
--- a/nixos/modules/services/web-apps/cloudlog.nix
+++ b/nixos/modules/services/web-apps/cloudlog.nix
@@ -69,7 +69,7 @@ let
 in
 {
   options.services.cloudlog = with types; {
-    enable = mkEnableOption (mdDoc "Whether to enable Cloudlog");
+    enable = mkEnableOption (mdDoc "Cloudlog");
     dataDir = mkOption {
       type = str;
       default = "/var/lib/cloudlog";
diff --git a/nixos/modules/services/web-apps/code-server.nix b/nixos/modules/services/web-apps/code-server.nix
deleted file mode 100644
index 11601f6c304..00000000000
--- a/nixos/modules/services/web-apps/code-server.nix
+++ /dev/null
@@ -1,259 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.code-server;
-  defaultUser = "code-server";
-  defaultGroup = defaultUser;
-in {
-  options = {
-    services.code-server = {
-      enable = lib.mkEnableOption (lib.mdDoc "code-server");
-
-      package = lib.mkPackageOptionMD pkgs "code-server" {
-        example = ''
-          pkgs.vscode-with-extensions.override {
-            vscode = pkgs.code-server;
-            vscodeExtensions = with pkgs.vscode-extensions; [
-              bbenoist.nix
-              dracula-theme.theme-dracula
-            ];
-          }
-        '';
-      };
-
-      extraPackages = lib.mkOption {
-        default = [ ];
-        description = lib.mdDoc ''
-          Additional packages to add to the code-server {env}`PATH`.
-        '';
-        example = lib.literalExpression "[ pkgs.go ]";
-        type = lib.types.listOf lib.types.package;
-      };
-
-      extraEnvironment = lib.mkOption {
-        type = lib.types.attrsOf lib.types.str;
-        description = lib.mdDoc ''
-          Additional environment variables to pass to code-server.
-        '';
-        default = { };
-        example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; };
-      };
-
-      extraArguments = lib.mkOption {
-        default = [ ];
-        description = lib.mdDoc ''
-          Additional arguments to pass to code-server.
-        '';
-        example = lib.literalExpression ''[ "--log=info" ]'';
-        type = lib.types.listOf lib.types.str;
-      };
-
-      host = lib.mkOption {
-        default = "localhost";
-        description = lib.mdDoc ''
-          The host name or IP address the server should listen to.
-        '';
-        type = lib.types.str;
-      };
-
-      port = lib.mkOption {
-        default = 4444;
-        description = lib.mdDoc ''
-          The port the server should listen to.
-        '';
-        type = lib.types.port;
-      };
-
-      auth = lib.mkOption {
-        default = "password";
-        description = lib.mdDoc ''
-          The type of authentication to use.
-        '';
-        type = lib.types.enum [ "none" "password" ];
-      };
-
-      hashedPassword = lib.mkOption {
-        default = "";
-        description = lib.mdDoc ''
-          Create the password with: `echo -n 'thisismypassword' | npx argon2-cli -e`.
-        '';
-        type = lib.types.str;
-      };
-
-      user = lib.mkOption {
-        default = defaultUser;
-        example = "yourUser";
-        description = lib.mdDoc ''
-          The user to run code-server as.
-          By default, a user named `${defaultUser}` will be created.
-        '';
-        type = lib.types.str;
-      };
-
-      group = lib.mkOption {
-        default = defaultGroup;
-        example = "yourGroup";
-        description = lib.mdDoc ''
-          The group to run code-server under.
-          By default, a group named `${defaultGroup}` will be created.
-        '';
-        type = lib.types.str;
-      };
-
-      extraGroups = lib.mkOption {
-        default = [ ];
-        description = lib.mdDoc ''
-          An array of additional groups for the `${defaultUser}` user.
-        '';
-        example = [ "docker" ];
-        type = lib.types.listOf lib.types.str;
-      };
-
-      socket = lib.mkOption {
-        default = null;
-        example = "/run/code-server/socket";
-        description = lib.mdDoc ''
-          Path to a socket (bind-addr will be ignored).
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      socketMode = lib.mkOption {
-        default = null;
-        description = lib.mdDoc ''
-           File mode of the socket.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      userDataDir = lib.mkOption {
-        default = null;
-        description = lib.mdDoc ''
-          Path to the user data directory.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      extensionsDir = lib.mkOption {
-        default = null;
-        description = lib.mdDoc ''
-          Path to the extensions directory.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      proxyDomain = lib.mkOption {
-        default = null;
-        example = "code-server.lan";
-        description = lib.mdDoc ''
-          Domain used for proxying ports.
-        '';
-        type = lib.types.nullOr lib.types.str;
-      };
-
-      disableTelemetry = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable telemetry.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableUpdateCheck = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable update check.
-          Without this flag, code-server checks every 6 hours against the latest github release and
-          then notifies you once every week that a new release is available.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableFileDownloads = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable file downloads from Code.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableWorkspaceTrust = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable Workspace Trust feature.
-        '';
-        type = lib.types.bool;
-      };
-
-      disableGettingStartedOverride = lib.mkOption {
-        default = false;
-        example = true;
-        description = lib.mdDoc ''
-          Disable the coder/coder override in the Help: Getting Started page.
-        '';
-        type = lib.types.bool;
-      };
-
-    };
-  };
-
-  config = lib.mkIf cfg.enable {
-    systemd.services.code-server = {
-      description = "Code server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network-online.target" ];
-      path = cfg.extraPackages;
-      environment = {
-        HASHED_PASSWORD = cfg.hashedPassword;
-      } // cfg.extraEnvironment;
-      serviceConfig = {
-        ExecStart = ''
-          ${lib.getExe cfg.package} \
-            --auth=${cfg.auth} \
-            --bind-addr=${cfg.host}:${toString cfg.port} \
-          '' + lib.optionalString (cfg.socket != null) ''
-            --socket=${cfg.socket} \
-          '' + lib.optionalString (cfg.userDataDir != null) ''
-            --user-data-dir=${cfg.userDataDir} \
-          '' + lib.optionalString (cfg.extensionsDir != null) ''
-            --extensions-dir=${cfg.extensionsDir} \
-          '' + lib.optionalString (cfg.disableTelemetry == true) ''
-            --disable-telemetry \
-          '' + lib.optionalString (cfg.disableUpdateCheck == true) ''
-            --disable-update-check \
-          '' + lib.optionalString (cfg.disableFileDownloads == true) ''
-            --disable-file-downloads \
-          '' + lib.optionalString (cfg.disableWorkspaceTrust == true) ''
-            --disable-workspace-trust \
-          '' + lib.optionalString (cfg.disableGettingStartedOverride == true) ''
-            --disable-getting-started-override \
-          '' + lib.escapeShellArgs cfg.extraArguments;
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        RuntimeDirectory = cfg.user;
-        User = cfg.user;
-        Group = cfg.group;
-        Restart = "on-failure";
-      };
-    };
-
-    users.users."${cfg.user}" = lib.mkMerge [
-      (lib.mkIf (cfg.user == defaultUser) {
-        isNormalUser = true;
-        description = "code-server user";
-        inherit (cfg) group;
-      })
-      {
-        packages = cfg.extraPackages;
-        inherit (cfg) extraGroups;
-      }
-    ];
-
-    users.groups."${defaultGroup}" = lib.mkIf (cfg.group == defaultGroup) { };
-  };
-
-  meta.maintainers = [ lib.maintainers.stackshadow ];
-}
diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix
index 469a29bc3aa..f65211308c4 100644
--- a/nixos/modules/services/web-apps/coder.nix
+++ b/nixos/modules/services/web-apps/coder.nix
@@ -149,8 +149,8 @@ in {
 
   config = mkIf cfg.enable {
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.username == name;
-        message = "services.coder.database.username must be set to ${user} if services.coder.database.createLocally is set true";
+      { assertion = cfg.database.createLocally -> cfg.database.username == name && cfg.database.database == cfg.database.username;
+        message = "services.coder.database.username must be set to ${name} if services.coder.database.createLocally is set true";
       }
     ];
 
@@ -193,10 +193,8 @@ in {
         cfg.database.database
       ];
       ensureUsers = [{
-        name = cfg.database.username;
-        ensurePermissions = {
-          "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES";
-        };
+        name = cfg.user;
+        ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix
index bd041db007a..0c4a71c6dfe 100644
--- a/nixos/modules/services/web-apps/dex.nix
+++ b/nixos/modules/services/web-apps/dex.nix
@@ -108,8 +108,7 @@ in
         ProtectClock = true;
         ProtectHome = true;
         ProtectHostname = true;
-        # Would re-mount paths ignored by temporary root
-        #ProtectSystem = "strict";
+        ProtectSystem = "strict";
         ProtectControlGroups = true;
         ProtectKernelLogs = true;
         ProtectKernelModules = true;
@@ -121,9 +120,7 @@ in
         RestrictSUIDSGID = true;
         SystemCallArchitectures = "native";
         SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ];
-        TemporaryFileSystem = "/:ro";
-        # Does not work well with the temporary root
-        #UMask = "0066";
+        UMask = "0066";
       } // optionalAttrs (cfg.environmentFile != null) {
         EnvironmentFile = cfg.environmentFile;
       };
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
index f80eb6b4c7f..da1dba7d940 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -407,7 +407,7 @@ in
             type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]);
             default = null;
             description = lib.mdDoc ''
-              Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+              Authentication type to use, see https://api.rubyonrails.org/classes/ActionMailer/Base.html
             '';
           };
 
@@ -423,7 +423,7 @@ in
             type = lib.types.str;
             default = "peer";
             description = lib.mdDoc ''
-              How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+              How OpenSSL checks the certificate, see https://api.rubyonrails.org/classes/ActionMailer/Base.html
             '';
           };
 
diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix
index ffc05d0e41f..8b4ea2aa53c 100644
--- a/nixos/modules/services/web-apps/freshrss.nix
+++ b/nixos/modules/services/web-apps/freshrss.nix
@@ -220,7 +220,7 @@ in
             "catch_workers_output" = true;
           };
           phpEnv = {
-            FRESHRSS_DATA_PATH = "${cfg.dataDir}";
+            DATA_PATH = "${cfg.dataDir}";
           };
         };
       };
@@ -267,7 +267,7 @@ in
             WorkingDirectory = cfg.package;
           };
           environment = {
-            FRESHRSS_DATA_PATH = cfg.dataDir;
+            DATA_PATH = cfg.dataDir;
           };
 
           script =
@@ -302,7 +302,7 @@ in
         wantedBy = [ "multi-user.target" ];
         startAt = "*:0/5";
         environment = {
-          FRESHRSS_DATA_PATH = cfg.dataDir;
+          DATA_PATH = cfg.dataDir;
         };
         serviceConfig = defaultServiceConfig //{
           ExecStart = "${cfg.package}/app/actualize_script.php";
diff --git a/nixos/modules/services/web-apps/gotosocial.nix b/nixos/modules/services/web-apps/gotosocial.nix
index f7ae018d5b7..9c21719a575 100644
--- a/nixos/modules/services/web-apps/gotosocial.nix
+++ b/nixos/modules/services/web-apps/gotosocial.nix
@@ -128,9 +128,7 @@ in
       ensureUsers = [
         {
           name = "gotosocial";
-          ensurePermissions = {
-            "DATABASE gotosocial" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/grocy.nix b/nixos/modules/services/web-apps/grocy.nix
index 4d1084e295f..fe40a3c2094 100644
--- a/nixos/modules/services/web-apps/grocy.nix
+++ b/nixos/modules/services/web-apps/grocy.nix
@@ -8,6 +8,8 @@ in {
   options.services.grocy = {
     enable = mkEnableOption (lib.mdDoc "grocy");
 
+    package = mkPackageOptionMD pkgs "grocy" { };
+
     hostName = mkOption {
       type = types.str;
       description = lib.mdDoc ''
@@ -143,7 +145,7 @@ in {
     services.nginx = {
       enable = true;
       virtualHosts."${cfg.hostName}" = mkMerge [
-        { root = "${pkgs.grocy}/public";
+        { root = "${cfg.package}/public";
           locations."/".extraConfig = ''
             rewrite ^ /index.php;
           '';
diff --git a/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix
index bfa5fd5aff2..1a66f077b09 100644
--- a/nixos/modules/services/web-apps/hedgedoc.nix
+++ b/nixos/modules/services/web-apps/hedgedoc.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) literalExpression mdDoc mkEnableOption mkIf mkOption mkPackageOptionMD mkRenamedOptionModule types versionAtLeast;
+  inherit (lib) mkOption types mdDoc literalExpression;
 
   cfg = config.services.hedgedoc;
 
@@ -9,990 +9,189 @@ let
   # versionAtLeast statement remains set to 21.03 for backwards compatibility.
   # See https://github.com/NixOS/nixpkgs/pull/108899 and
   # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
-  name = if versionAtLeast config.system.stateVersion "21.03"
-    then "hedgedoc"
-    else "codimd";
+  name = if lib.versionAtLeast config.system.stateVersion "21.03" then
+    "hedgedoc"
+  else
+    "codimd";
 
-  settingsFormat = pkgs.formats.json {};
-
-  prettyJSON = conf:
-    pkgs.runCommandLocal "hedgedoc-config.json" {
-      nativeBuildInputs = [ pkgs.jq ];
-    } ''
-      jq '{production:del(.[]|nulls)|del(.[][]?|nulls)}' \
-        < ${settingsFormat.generate "hedgedoc-ugly.json" cfg.settings} \
-        > $out
-    '';
+  settingsFormat = pkgs.formats.json { };
 in
 {
+  meta.maintainers = with lib.maintainers; [ SuperSandro2000 h7x4 ];
+
   imports = [
-    (mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
-    (mkRenamedOptionModule
-      [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
+    (lib.mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
+    (lib.mkRenamedOptionModule [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
+    (lib.mkRenamedOptionModule [ "services" "hedgedoc" "groups" ] [ "users" "users" "hedgedoc" "extraGroups" ])
+    (lib.mkRemovedOptionModule [ "services" "hedgedoc" "workDir" ] ''
+      This option has been removed in favor of systemd managing the state directory.
+
+      If you have set this option without specifying `services.settings.uploadsDir`,
+      please move these files to `/var/lib/hedgedoc/uploads`, or set the option to point
+      at the correct location.
+    '')
   ];
 
   options.services.hedgedoc = {
-    package = mkPackageOptionMD pkgs "hedgedoc" { };
-    enable = mkEnableOption (lib.mdDoc "the HedgeDoc Markdown Editor");
+    package = lib.mkPackageOptionMD pkgs "hedgedoc" { };
+    enable = lib.mkEnableOption (mdDoc "the HedgeDoc Markdown Editor");
 
-    groups = mkOption {
-      type = types.listOf types.str;
-      default = [];
-      description = lib.mdDoc ''
-        Groups to which the service user should be added.
-      '';
-    };
-
-    workDir = mkOption {
-      type = types.path;
-      default = "/var/lib/${name}";
-      description = lib.mdDoc ''
-        Working directory for the HedgeDoc service.
-      '';
-    };
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          domain = mkOption {
+            type = with types; nullOr str;
+            default = null;
+            example = "hedgedoc.org";
+            description = mdDoc ''
+              Domain to use for website.
 
-    settings = let options = {
-      debug = mkEnableOption (lib.mdDoc "debug mode");
-      domain = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "hedgedoc.org";
-        description = lib.mdDoc ''
-          Domain name for the HedgeDoc instance.
-        '';
-      };
-      urlPath = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/url/path/to/hedgedoc";
-        description = lib.mdDoc ''
-          Path under which HedgeDoc is accessible.
-        '';
-      };
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = lib.mdDoc ''
-          Address to listen on.
-        '';
-      };
-      port = mkOption {
-        type = types.port;
-        default = 3000;
-        example = 80;
-        description = lib.mdDoc ''
-          Port to listen on.
-        '';
-      };
-      path = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/run/hedgedoc.sock";
-        description = lib.mdDoc ''
-          Specify where a UNIX domain socket should be placed.
-        '';
-      };
-      allowOrigin = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "localhost" "hedgedoc.org" ];
-        description = lib.mdDoc ''
-          List of domains to whitelist.
-        '';
-      };
-      useSSL = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable to use SSL server. This will also enable
-          {option}`protocolUseSSL`.
-        '';
-      };
-      enableStatsApi = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enables or disables the /status and /metrics endpoint.
-        '';
-      };
-      hsts = {
-        enable = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to enable HSTS if HTTPS is also enabled.
-          '';
-        };
-        maxAgeSeconds = mkOption {
-          type = types.int;
-          default = 31536000;
-          description = lib.mdDoc ''
-            Max duration for clients to keep the HSTS status.
-          '';
-        };
-        includeSubdomains = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to include subdomains in HSTS.
-          '';
-        };
-        preload = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to allow preloading of the site's HSTS status.
-          '';
-        };
-      };
-      csp = mkOption {
-        type = types.nullOr types.attrs;
-        default = null;
-        example = literalExpression ''
-          {
-            enable = true;
-            directives = {
-              scriptSrc = "trustworthy.scripts.example.com";
-            };
-            upgradeInsecureRequest = "auto";
-            addDefaults = true;
-          }
-        '';
-        description = lib.mdDoc ''
-          Specify the Content Security Policy which is passed to Helmet.
-          For configuration details see <https://helmetjs.github.io/docs/csp/>.
-        '';
-      };
-      protocolUseSSL = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable to use TLS for resource paths.
-          This only applies when {option}`domain` is set.
-        '';
-      };
-      urlAddPort = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable to add the port to callback URLs.
-          This only applies when {option}`domain` is set
-          and only for ports other than 80 and 443.
-        '';
-      };
-      useCDN = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to use CDN resources or not.
-        '';
-      };
-      allowAnonymous = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to allow anonymous usage.
-        '';
-      };
-      allowAnonymousEdits = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to allow guests to edit existing notes with the `freely` permission,
-          when {option}`allowAnonymous` is enabled.
-        '';
-      };
-      allowFreeURL = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to allow note creation by accessing a nonexistent note URL.
-        '';
-      };
-      requireFreeURLAuthentication = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether to require authentication for FreeURL mode style note creation.
-        '';
-      };
-      defaultPermission = mkOption {
-        type = types.enum [ "freely" "editable" "limited" "locked" "private" ];
-        default = "editable";
-        description = lib.mdDoc ''
-          Default permissions for notes.
-          This only applies for signed-in users.
-        '';
-      };
-      dbURL = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = ''
-          postgres://user:pass@host:5432/dbname
-        '';
-        description = lib.mdDoc ''
-          Specify which database to use.
-          HedgeDoc supports mysql, postgres, sqlite and mssql.
-          See [
-          https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
-          Note: This option overrides {option}`db`.
-        '';
-      };
-      db = mkOption {
-        type = types.attrs;
-        default = {};
-        example = literalExpression ''
-          {
-            dialect = "sqlite";
-            storage = "/var/lib/${name}/db.${name}.sqlite";
-          }
-        '';
-        description = lib.mdDoc ''
-          Specify the configuration for sequelize.
-          HedgeDoc supports mysql, postgres, sqlite and mssql.
-          See [
-          https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
-          Note: This option overrides {option}`db`.
-        '';
-      };
-      sslKeyPath= mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/var/lib/hedgedoc/hedgedoc.key";
-        description = lib.mdDoc ''
-          Path to the SSL key. Needed when {option}`useSSL` is enabled.
-        '';
-      };
-      sslCertPath = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/var/lib/hedgedoc/hedgedoc.crt";
-        description = lib.mdDoc ''
-          Path to the SSL cert. Needed when {option}`useSSL` is enabled.
-        '';
-      };
-      sslCAPath = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "/var/lib/hedgedoc/ca.crt" ];
-        description = lib.mdDoc ''
-          SSL ca chain. Needed when {option}`useSSL` is enabled.
-        '';
-      };
-      dhParamPath = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/var/lib/hedgedoc/dhparam.pem";
-        description = lib.mdDoc ''
-          Path to the SSL dh params. Needed when {option}`useSSL` is enabled.
-        '';
-      };
-      tmpPath = mkOption {
-        type = types.str;
-        default = "/tmp";
-        description = lib.mdDoc ''
-          Path to the temp directory HedgeDoc should use.
-          Note that {option}`serviceConfig.PrivateTmp` is enabled for
-          the HedgeDoc systemd service by default.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      defaultNotePath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/default.md";
-        defaultText = literalExpression "\"\${cfg.package}/public/default.md\"";
-        description = lib.mdDoc ''
-          Path to the default Note file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      docsPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/docs";
-        defaultText = literalExpression "\"\${cfg.package}/public/docs\"";
-        description = lib.mdDoc ''
-          Path to the docs directory.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      indexPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/index.ejs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/index.ejs\"";
-        description = lib.mdDoc ''
-          Path to the index template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      hackmdPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/hackmd.ejs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/hackmd.ejs\"";
-        description = lib.mdDoc ''
-          Path to the hackmd template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      errorPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/error.ejs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/error.ejs\"";
-        description = lib.mdDoc ''
-          Path to the error template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      prettyPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/pretty.ejs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/pretty.ejs\"";
-        description = lib.mdDoc ''
-          Path to the pretty template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      slidePath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/slide.hbs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/slide.hbs\"";
-        description = lib.mdDoc ''
-          Path to the slide template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      uploadsPath = mkOption {
-        type = types.str;
-        default = "${cfg.workDir}/uploads";
-        defaultText = literalExpression "\"\${cfg.workDir}/uploads\"";
-        description = lib.mdDoc ''
-          Path under which uploaded files are saved.
-        '';
-      };
-      sessionName = mkOption {
-        type = types.str;
-        default = "connect.sid";
-        description = lib.mdDoc ''
-          Specify the name of the session cookie.
-        '';
-      };
-      sessionSecret = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc ''
-          Specify the secret used to sign the session cookie.
-          If unset, one will be generated on startup.
-        '';
-      };
-      sessionLife = mkOption {
-        type = types.int;
-        default = 1209600000;
-        description = lib.mdDoc ''
-          Session life time in milliseconds.
-        '';
-      };
-      heartbeatInterval = mkOption {
-        type = types.int;
-        default = 5000;
-        description = lib.mdDoc ''
-          Specify the socket.io heartbeat interval.
-        '';
-      };
-      heartbeatTimeout = mkOption {
-        type = types.int;
-        default = 10000;
-        description = lib.mdDoc ''
-          Specify the socket.io heartbeat timeout.
-        '';
-      };
-      documentMaxLength = mkOption {
-        type = types.int;
-        default = 100000;
-        description = lib.mdDoc ''
-          Specify the maximum document length.
-        '';
-      };
-      email = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to enable email sign-in.
-        '';
-      };
-      allowEmailRegister = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to enable email registration.
-        '';
-      };
-      allowGravatar = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to use gravatar as profile picture source.
-        '';
-      };
-      imageUploadType = mkOption {
-        type = types.enum [ "imgur" "s3" "minio" "filesystem" ];
-        default = "filesystem";
-        description = lib.mdDoc ''
-          Specify where to upload images.
-        '';
-      };
-      minio = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            accessKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Minio access key.
-              '';
-            };
-            secretKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Minio secret key.
-              '';
-            };
-            endPoint = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Minio endpoint.
-              '';
-            };
-            port = mkOption {
-              type = types.port;
-              default = 9000;
-              description = lib.mdDoc ''
-                Minio listen port.
-              '';
-            };
-            secure = mkOption {
-              type = types.bool;
-              default = true;
-              description = lib.mdDoc ''
-                Whether to use HTTPS for Minio.
-              '';
-            };
+              This is useful if you are trying to run hedgedoc behind
+              a reverse proxy.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the minio third-party integration.";
-      };
-      s3 = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            accessKeyId = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                AWS access key id.
-              '';
-            };
-            secretAccessKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                AWS access key.
-              '';
-            };
-            region = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                AWS S3 region.
-              '';
-            };
-          };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the s3 third-party integration.";
-      };
-      s3bucket = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc ''
-          Specify the bucket name for upload types `s3` and `minio`.
-        '';
-      };
-      allowPDFExport = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to enable PDF exports.
-        '';
-      };
-      imgur.clientId = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc ''
-          Imgur API client ID.
-        '';
-      };
-      azure = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            connectionString = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Azure Blob Storage connection string.
-              '';
-            };
-            container = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Azure Blob Storage container name.
-                It will be created if non-existent.
-              '';
-            };
-          };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the azure third-party integration.";
-      };
-      oauth2 = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            authorizationURL = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Specify the OAuth authorization URL.
-              '';
-            };
-            tokenURL = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Specify the OAuth token URL.
-              '';
-            };
-            baseURL = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the OAuth base URL.
-              '';
-            };
-            userProfileURL = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the OAuth userprofile URL.
-              '';
-            };
-            userProfileUsernameAttr = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the name of the attribute for the username from the claim.
-              '';
-            };
-            userProfileDisplayNameAttr = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the name of the attribute for the display name from the claim.
-              '';
-            };
-            userProfileEmailAttr = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the name of the attribute for the email from the claim.
-              '';
-            };
-            scope = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the OAuth scope.
-              '';
-            };
-            providerName = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the name to be displayed for this strategy.
-              '';
-            };
-            rolesClaim = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the role claim name.
-              '';
-            };
-            accessRole = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify role which should be included in the ID token roles claim to grant access
-              '';
-            };
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Specify the OAuth client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the OAuth client secret.
-              '';
-            };
+          urlPath = mkOption {
+            type = with types; nullOr str;
+            default = null;
+            example = "hedgedoc";
+            description = mdDoc ''
+              URL path for the website.
+
+              This is useful if you are hosting hedgedoc on a path like
+              `www.example.com/hedgedoc`
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the OAuth integration.";
-      };
-      facebook = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Facebook API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Facebook API client secret.
-              '';
-            };
+          host = mkOption {
+            type = with types; nullOr str;
+            default = "localhost";
+            description = mdDoc ''
+              Address to listen on.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the facebook third-party integration";
-      };
-      twitter = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            consumerKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Twitter API consumer key.
-              '';
-            };
-            consumerSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Twitter API consumer secret.
-              '';
-            };
+          port = mkOption {
+            type = types.port;
+            default = 3000;
+            example = 80;
+            description = mdDoc ''
+              Port to listen on.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the Twitter third-party integration.";
-      };
-      github = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                GitHub API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Github API client secret.
-              '';
-            };
+          path = mkOption {
+            type = with types; nullOr path;
+            default = null;
+            example = "/run/hedgedoc/hedgedoc.sock";
+            description = mdDoc ''
+              Path to UNIX domain socket to listen on
+
+              ::: {.note}
+                If specified, {option}`host` and {option}`port` will be ignored.
+              :::
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the GitHub third-party integration.";
-      };
-      gitlab = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            baseURL = mkOption {
-              type = types.str;
-              default = "";
-              description = lib.mdDoc ''
-                GitLab API authentication endpoint.
-                Only needed for other endpoints than gitlab.com.
-              '';
-            };
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                GitLab API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                GitLab API client secret.
-              '';
-            };
-            scope = mkOption {
-              type = types.enum [ "api" "read_user" ];
-              default = "api";
-              description = lib.mdDoc ''
-                GitLab API requested scope.
-                GitLab snippet import/export requires api scope.
-              '';
-            };
+          protocolUseSSL = mkOption {
+            type = types.bool;
+            default = false;
+            example = true;
+            description = mdDoc ''
+              Use `https://` for all links.
+
+              This is useful if you are trying to run hedgedoc behind
+              a reverse proxy.
+
+              ::: {.note}
+                Only applied if {option}`domain` is set.
+              :::
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the GitLab third-party integration.";
-      };
-      mattermost = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            baseURL = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Mattermost authentication endpoint.
-              '';
-            };
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Mattermost API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Mattermost API client secret.
-              '';
-            };
+          allowOrigin = mkOption {
+            type = with types; listOf str;
+            default = with cfg.settings; [ host ] ++ lib.optionals (domain != null) [ domain ];
+            defaultText = literalExpression ''
+              with config.services.hedgedoc.settings; [ host ] ++ lib.optionals (domain != null) [ domain ]
+            '';
+            example = [ "localhost" "hedgedoc.org" ];
+            description = mdDoc ''
+              List of domains to whitelist.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the Mattermost third-party integration.";
-      };
-      dropbox = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Dropbox API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Dropbox API client secret.
-              '';
-            };
-            appKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Dropbox app key.
-              '';
-            };
+          db = mkOption {
+            type = types.attrs;
+            default = {
+              dialect = "sqlite";
+              storage = "/var/lib/${name}/db.sqlite";
+            };
+            defaultText = literalExpression ''
+              {
+                dialect = "sqlite";
+                storage = "/var/lib/hedgedoc/db.sqlite";
+              }
+            '';
+            example = literalExpression ''
+              db = {
+                username = "hedgedoc";
+                database = "hedgedoc";
+                host = "localhost:5432";
+                # or via socket
+                # host = "/run/postgresql";
+                dialect = "postgresql";
+              };
+            '';
+            description = mdDoc ''
+              Specify the configuration for sequelize.
+              HedgeDoc supports `mysql`, `postgres`, `sqlite` and `mssql`.
+              See <https://sequelize.readthedocs.io/en/v3/>
+              for more information.
+
+              ::: {.note}
+                The relevant parts will be overriden if you set {option}`dbURL`.
+              :::
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the Dropbox third-party integration.";
-      };
-      google = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Google API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Google API client secret.
-              '';
-            };
+          useSSL = mkOption {
+            type = types.bool;
+            default = false;
+            description = mdDoc ''
+              Enable to use SSL server.
+
+              ::: {.note}
+                This will also enable {option}`protocolUseSSL`.
+
+                It will also require you to set the following:
+
+                - {option}`sslKeyPath`
+                - {option}`sslCertPath`
+                - {option}`sslCAPath`
+                - {option}`dhParamPath`
+              :::
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the Google third-party integration.";
-      };
-      ldap = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            providerName = mkOption {
-              type = types.str;
-              default = "";
-              description = lib.mdDoc ''
-                Optional name to be displayed at login form, indicating the LDAP provider.
-              '';
-            };
-            url = mkOption {
-              type = types.str;
-              example = "ldap://localhost";
-              description = lib.mdDoc ''
-                URL of LDAP server.
-              '';
-            };
-            bindDn = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Bind DN for LDAP access.
-              '';
-            };
-            bindCredentials = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Bind credentials for LDAP access.
-              '';
-            };
-            searchBase = mkOption {
-              type = types.str;
-              example = "o=users,dc=example,dc=com";
-              description = lib.mdDoc ''
-                LDAP directory to begin search from.
-              '';
-            };
-            searchFilter = mkOption {
-              type = types.str;
-              example = "(uid={{username}})";
-              description = lib.mdDoc ''
-                LDAP filter to search with.
-              '';
-            };
-            searchAttributes = mkOption {
-              type = types.nullOr (types.listOf types.str);
-              default = null;
-              example = [ "displayName" "mail" ];
-              description = lib.mdDoc ''
-                LDAP attributes to search with.
-              '';
-            };
-            userNameField = mkOption {
-              type = types.str;
-              default = "";
-              description = lib.mdDoc ''
-                LDAP field which is used as the username on HedgeDoc.
-                By default {option}`useridField` is used.
-              '';
-            };
-            useridField = mkOption {
-              type = types.str;
-              example = "uid";
-              description = lib.mdDoc ''
-                LDAP field which is a unique identifier for users on HedgeDoc.
-              '';
-            };
-            tlsca = mkOption {
-              type = types.str;
-              default = "/etc/ssl/certs/ca-certificates.crt";
-              example = "server-cert.pem,root.pem";
-              description = lib.mdDoc ''
-                Root CA for LDAP TLS in PEM format.
-              '';
-            };
+          uploadsPath = mkOption {
+            type = types.path;
+            default = "/var/lib/${name}/uploads";
+            defaultText = "/var/lib/hedgedoc/uploads";
+            description = mdDoc ''
+              Directory for storing uploaded images.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the LDAP integration.";
-      };
-      saml = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            idpSsoUrl = mkOption {
-              type = types.str;
-              example = "https://idp.example.com/sso";
-              description = lib.mdDoc ''
-                IdP authentication endpoint.
-              '';
-            };
-            idpCert = mkOption {
-              type = types.path;
-              example = "/path/to/cert.pem";
-              description = lib.mdDoc ''
-                Path to IdP certificate file in PEM format.
-              '';
-            };
-            issuer = mkOption {
-              type = types.str;
-              default = "";
-              description = lib.mdDoc ''
-                Optional identity of the service provider.
-                This defaults to the server URL.
-              '';
-            };
-            identifierFormat = mkOption {
-              type = types.str;
-              default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
-              description = lib.mdDoc ''
-                Optional name identifier format.
-              '';
-            };
-            groupAttribute = mkOption {
-              type = types.str;
-              default = "";
-              example = "memberOf";
-              description = lib.mdDoc ''
-                Optional attribute name for group list.
-              '';
-            };
-            externalGroups = mkOption {
-              type = types.listOf types.str;
-              default = [];
-              example = [ "Temporary-staff" "External-users" ];
-              description = lib.mdDoc ''
-                Excluded group names.
-              '';
-            };
-            requiredGroups = mkOption {
-              type = types.listOf types.str;
-              default = [];
-              example = [ "Hedgedoc-Users" ];
-              description = lib.mdDoc ''
-                Required group names.
-              '';
-            };
-            providerName = mkOption {
-              type = types.str;
-              default = "";
-              example = "My institution";
-              description = lib.mdDoc ''
-                Optional name to be displayed at login form indicating the SAML provider.
-              '';
-            };
-            attribute = {
-              id = mkOption {
-                type = types.str;
-                default = "";
-                description = lib.mdDoc ''
-                  Attribute map for `id`.
-                  Defaults to `NameID` of SAML response.
-                '';
-              };
-              username = mkOption {
-                type = types.str;
-                default = "";
-                description = lib.mdDoc ''
-                  Attribute map for `username`.
-                  Defaults to `NameID` of SAML response.
-                '';
-              };
-              email = mkOption {
-                type = types.str;
-                default = "";
-                description = lib.mdDoc ''
-                  Attribute map for `email`.
-                  Defaults to `NameID` of SAML response if
-                  {option}`identifierFormat` has
-                  the default value.
-                '';
-              };
-            };
+
+          # Declared because we change the default to false.
+          allowGravatar = mkOption {
+            type = types.bool;
+            default = false;
+            example = true;
+            description = mdDoc ''
+              Whether to enable [Libravatar](https://wiki.libravatar.org/) as
+              profile picture source on your instance.
+
+              Despite the naming of the setting, Hedgedoc replaced Gravatar
+              with Libravatar in [CodiMD 1.4.0](https://hedgedoc.org/releases/1.4.0/)
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the SAML integration.";
-      };
-    }; in lib.mkOption {
-      type = lib.types.submodule {
-        freeformType = settingsFormat.type;
-        inherit options;
+        };
       };
-      description = lib.mdDoc ''
+
+      description = mdDoc ''
         HedgeDoc configuration, see
         <https://docs.hedgedoc.org/configuration/>
         for documentation.
@@ -1003,7 +202,7 @@ in
       type = with types; nullOr path;
       default = null;
       example = "/var/lib/hedgedoc/hedgedoc.env";
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Environment file as defined in {manpage}`systemd.exec(5)`.
 
         Secrets may be passed to the service without adding them to the world-readable
@@ -1028,45 +227,94 @@ in
     };
   };
 
-  config = mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.settings.db == {} -> (
-          cfg.settings.dbURL != "" && cfg.settings.dbURL != null
-        );
-        message = "Database configuration for HedgeDoc missing."; }
-    ];
-    users.groups.${name} = {};
+  config = lib.mkIf cfg.enable {
+    users.groups.${name} = { };
     users.users.${name} = {
       description = "HedgeDoc service user";
       group = name;
-      extraGroups = cfg.groups;
-      home = cfg.workDir;
-      createHome = true;
       isSystemUser = true;
     };
 
+    services.hedgedoc.settings = {
+      defaultNotePath = lib.mkDefault "${cfg.package}/public/default.md";
+      docsPath = lib.mkDefault "${cfg.package}/public/docs";
+      viewPath = lib.mkDefault "${cfg.package}/public/views";
+    };
+
     systemd.services.hedgedoc = {
       description = "HedgeDoc Service";
+      documentation = [ "https://docs.hedgedoc.org/" ];
       wantedBy = [ "multi-user.target" ];
       after = [ "networking.target" ];
-      preStart = ''
-        ${pkgs.envsubst}/bin/envsubst \
-          -o ${cfg.workDir}/config.json \
-          -i ${prettyJSON cfg.settings}
-        mkdir -p ${cfg.settings.uploadsPath}
-      '';
+      preStart =
+        let
+          configFile = settingsFormat.generate "hedgedoc-config.json" {
+            production = cfg.settings;
+          };
+        in
+        ''
+          ${pkgs.envsubst}/bin/envsubst \
+            -o /run/${name}/config.json \
+            -i ${configFile}
+          ${pkgs.coreutils}/bin/mkdir -p ${cfg.settings.uploadsPath}
+        '';
       serviceConfig = {
-        WorkingDirectory = cfg.workDir;
-        StateDirectory = [ cfg.workDir cfg.settings.uploadsPath ];
-        ExecStart = "${lib.getExe cfg.package}";
-        EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+        User = name;
+        Group = name;
+
+        Restart = "always";
+        ExecStart = "${cfg.package}/bin/hedgedoc";
+        RuntimeDirectory = [ name ];
+        StateDirectory = [ name ];
+        WorkingDirectory = "/run/${name}";
+        ReadWritePaths = [
+          "-${cfg.settings.uploadsPath}"
+        ] ++ lib.optionals (cfg.settings.db ? "storage") [ "-${cfg.settings.db.storage}" ];
+        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
         Environment = [
-          "CMD_CONFIG_FILE=${cfg.workDir}/config.json"
+          "CMD_CONFIG_FILE=/run/${name}/config.json"
           "NODE_ENV=production"
         ];
-        Restart = "always";
-        User = name;
+
+        # Hardening
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
         PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          # Required for connecting to database sockets,
+          # and listening to unix socket at `cfg.settings.path`
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SocketBindAllow = lib.mkIf (cfg.settings.path == null) cfg.settings.port;
+        SocketBindDeny = "any";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged @obsolete"
+          "@pkey"
+        ];
+        UMask = "0007";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/hledger-web.nix b/nixos/modules/services/web-apps/hledger-web.nix
index 0fc283ff521..be8ecc645e5 100644
--- a/nixos/modules/services/web-apps/hledger-web.nix
+++ b/nixos/modules/services/web-apps/hledger-web.nix
@@ -7,7 +7,7 @@ in {
 
     enable = mkEnableOption (lib.mdDoc "hledger-web service");
 
-    serveApi = mkEnableOption (lib.mdDoc "Serve only the JSON web API, without the web UI");
+    serveApi = mkEnableOption (lib.mdDoc "serving only the JSON web API, without the web UI");
 
     host = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/web-apps/honk.nix b/nixos/modules/services/web-apps/honk.nix
index e8718774575..d47b17e54ff 100644
--- a/nixos/modules/services/web-apps/honk.nix
+++ b/nixos/modules/services/web-apps/honk.nix
@@ -116,7 +116,7 @@ in
       unitConfig = {
         ConditionPathExists = [
           # Skip this service if the database already exists
-          "!$STATE_DIRECTORY/honk.db"
+          "!%S/honk/honk.db"
         ];
       };
     };
diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix
index 5603ef7392e..e4fbc6fd936 100644
--- a/nixos/modules/services/web-apps/invidious.nix
+++ b/nixos/modules/services/web-apps/invidious.nix
@@ -109,15 +109,17 @@ let
     # Default to using the local database if we create it
     services.invidious.database.host = lib.mkDefault null;
 
+
+    # TODO(raitobezarius to maintainers of invidious): I strongly advise to clean up the kemal specific
+    # thing for 24.05 and use `ensureDBOwnership`.
+    # See https://github.com/NixOS/nixpkgs/issues/216989
+    systemd.services.postgresql.postStart = lib.mkAfter ''
+      $PSQL -tAc 'ALTER DATABASE "${cfg.settings.db.dbname}" OWNER TO "${cfg.settings.db.user}";'
+    '';
     services.postgresql = {
       enable = true;
+      ensureUsers = lib.singleton { name = cfg.settings.db.user; ensureDBOwnership = false; };
       ensureDatabases = lib.singleton cfg.settings.db.dbname;
-      ensureUsers = lib.singleton {
-        name = cfg.settings.db.user;
-        ensurePermissions = {
-          "DATABASE ${cfg.settings.db.dbname}" = "ALL PRIVILEGES";
-        };
-      };
       # This is only needed because the unix user invidious isn't the same as
       # the database user. This tells postgres to map one to the other.
       identMap = ''
@@ -136,6 +138,7 @@ let
       documentation = [ "https://docs.invidious.io/Database-Information-and-Maintenance.md" ];
       startAt = lib.mkDefault "weekly";
       path = [ config.services.postgresql.package ];
+      after = [ "postgresql.service" ];
       script = ''
         psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "DELETE FROM nonces * WHERE expire < current_timestamp"
         psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "TRUNCATE TABLE videos"
diff --git a/nixos/modules/services/web-apps/isso.nix b/nixos/modules/services/web-apps/isso.nix
index 1a852ec352f..6cb2d9ec785 100644
--- a/nixos/modules/services/web-apps/isso.nix
+++ b/nixos/modules/services/web-apps/isso.nix
@@ -12,11 +12,11 @@ in {
   options = {
     services.isso = {
       enable = mkEnableOption (lib.mdDoc ''
-        A commenting server similar to Disqus.
+        isso, a commenting server similar to Disqus.
 
         Note: The application's author suppose to run isso behind a reverse proxy.
         The embedded solution offered by NixOS is also only suitable for small installations
-        below 20 requests per second.
+        below 20 requests per second
       '');
 
       settings = mkOption {
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 3825b03c244..c0f9d785eea 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -105,9 +105,9 @@ in
         type = bool;
         default = true;
         description = lib.mdDoc ''
-          Whether to enable Jitsi Videobridge instance and configure it to connect to Prosody.
+          Jitsi Videobridge instance and configure it to connect to Prosody.
 
-          Additional configuration is possible with {option}`services.jitsi-videobridge`.
+          Additional configuration is possible with {option}`services.jitsi-videobridge`
         '';
       };
 
@@ -169,6 +169,15 @@ in
         off if you want to configure it manually.
       '';
     };
+
+    excalidraw.enable = mkEnableOption (lib.mdDoc "Excalidraw collaboration backend for Jitsi");
+    excalidraw.port = mkOption {
+      type = types.port;
+      default = 3002;
+      description = lib.mdDoc ''The port which the Excalidraw backend for Jitsi should listen to.'';
+    };
+
+    secureDomain.enable = mkEnableOption (lib.mdDoc "Authenticated room creation");
   };
 
   config = mkIf cfg.enable {
@@ -192,41 +201,118 @@ in
           roomLocking = false;
           roomDefaultPublicJids = true;
           extraConfig = ''
+            restrict_room_creation = true
+            storage = "memory"
+            admins = { "focus@auth.${cfg.hostName}" }
+          '';
+        }
+        {
+          domain = "breakout.${cfg.hostName}";
+          name = "Jitsi Meet Breakout MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
+          extraConfig = ''
+            restrict_room_creation = true
             storage = "memory"
+            admins = { "focus@auth.${cfg.hostName}" }
           '';
         }
         {
-          domain = "internal.${cfg.hostName}";
+          domain = "internal.auth.${cfg.hostName}";
           name = "Jitsi Meet Videobridge MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
           extraConfig = ''
             storage = "memory"
             admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" }
           '';
           #-- muc_room_cache_size = 1000
         }
+        {
+          domain = "lobby.${cfg.hostName}";
+          name = "Jitsi Meet Lobby MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
+          extraConfig = ''
+            restrict_room_creation = true
+            storage = "memory"
+          '';
+        }
+      ];
+      extraModules = [
+        "pubsub"
+        "smacks"
+        "speakerstats"
+        "external_services"
+        "conference_duration"
+        "end_conference"
+        "muc_lobby_rooms"
+        "muc_breakout_rooms"
+        "av_moderation"
+        "muc_hide_all"
+        "muc_meeting_id"
+        "muc_domain_mapper"
+        "muc_rate_limit"
+        "limits_exception"
+        "persistent_lobby"
+        "room_metadata"
       ];
-      extraModules = [ "pubsub" "smacks" ];
       extraPluginPaths = [ "${pkgs.jitsi-meet-prosody}/share/prosody-plugins" ];
-      extraConfig = lib.mkMerge [ (mkAfter ''
-        Component "focus.${cfg.hostName}" "client_proxy"
-          target_address = "focus@auth.${cfg.hostName}"
+      extraConfig = lib.mkMerge [
+        (mkAfter ''
+          Component "focus.${cfg.hostName}" "client_proxy"
+            target_address = "focus@auth.${cfg.hostName}"
+
+          Component "speakerstats.${cfg.hostName}" "speakerstats_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "conferenceduration.${cfg.hostName}" "conference_duration_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "endconference.${cfg.hostName}" "end_conference"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "avmoderation.${cfg.hostName}" "av_moderation_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "metadata.${cfg.hostName}" "room_metadata_component"
+            muc_component = "conference.${cfg.hostName}"
+            breakout_rooms_component = "breakout.${cfg.hostName}"
         '')
         (mkBefore ''
+          muc_mapper_domain_base = "${cfg.hostName}"
+
           cross_domain_websocket = true;
           consider_websocket_secure = true;
+
+          unlimited_jids = {
+            "focus@auth.${cfg.hostName}",
+            "jvb@auth.${cfg.hostName}"
+          }
         '')
       ];
       virtualHosts.${cfg.hostName} = {
         enabled = true;
         domain = cfg.hostName;
         extraConfig = ''
-          authentication = "anonymous"
+          authentication = ${if cfg.secureDomain.enable then "\"internal_hashed\"" else "\"jitsi-anonymous\""}
           c2s_require_encryption = false
           admins = { "focus@auth.${cfg.hostName}" }
           smacks_max_unacked_stanzas = 5
           smacks_hibernation_time = 60
           smacks_max_hibernated_sessions = 1
           smacks_max_old_sessions = 1
+
+          av_moderation_component = "avmoderation.${cfg.hostName}"
+          speakerstats_component = "speakerstats.${cfg.hostName}"
+          conference_duration_component = "conferenceduration.${cfg.hostName}"
+          end_conference_component = "endconference.${cfg.hostName}"
+
+          c2s_require_encryption = false
+          lobby_muc = "lobby.${cfg.hostName}"
+          breakout_rooms_muc = "breakout.${cfg.hostName}"
+          room_metadata_component = "metadata.${cfg.hostName}"
+          main_muc = "conference.${cfg.hostName}"
         '';
         ssl = {
           cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
@@ -237,7 +323,7 @@ in
         enabled = true;
         domain = "auth.${cfg.hostName}";
         extraConfig = ''
-          authentication = "internal_plain"
+          authentication = "internal_hashed"
         '';
         ssl = {
           cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
@@ -252,6 +338,14 @@ in
           c2s_require_encryption = false
         '';
       };
+      virtualHosts."guest.${cfg.hostName}" = {
+        enabled = true;
+        domain = "guest.${cfg.hostName}";
+        extraConfig = ''
+          authentication = "anonymous"
+          c2s_require_encryption = false
+        '';
+      };
     };
     systemd.services.prosody = mkIf cfg.prosody.enable {
       preStart = let
@@ -270,7 +364,7 @@ in
       reloadIfChanged = true;
     };
 
-    users.groups.jitsi-meet = {};
+    users.groups.jitsi-meet = { };
     systemd.tmpfiles.rules = [
       "d '/var/lib/jitsi-meet' 0750 root jitsi-meet - -"
     ];
@@ -317,6 +411,20 @@ in
       '';
     };
 
+    systemd.services.jitsi-excalidraw = mkIf cfg.excalidraw.enable {
+      description = "Excalidraw collaboration backend for Jitsi";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.PORT = toString cfg.excalidraw.port;
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.jitsi-excalidraw}/bin/jitsi-excalidraw-backend";
+        Restart = "on-failure";
+        Group = "jitsi-meet";
+      };
+    };
+
     services.nginx = mkIf cfg.nginx.enable {
       enable = mkDefault true;
       virtualHosts.${cfg.hostName} = {
@@ -345,12 +453,23 @@ in
         locations."=/external_api.js" = mkDefault {
           alias = "${pkgs.jitsi-meet}/libs/external_api.min.js";
         };
+        locations."=/_api/room-info" = {
+          proxyPass = "http://localhost:5280/room-info";
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header Host $host;
+          '';
+        };
         locations."=/config.js" = mkDefault {
           alias = overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig;
         };
         locations."=/interface_config.js" = mkDefault {
           alias = overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig "";
         };
+        locations."/socket.io/" = mkIf cfg.excalidraw.enable {
+          proxyPass = "http://127.0.0.1:${toString cfg.excalidraw.port}";
+          proxyWebsockets = true;
+        };
       };
     };
 
@@ -359,7 +478,7 @@ in
       virtualHosts.${cfg.hostName} = {
         extraConfig =
         let
-          templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" {} ''
+          templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" { } ''
             cp -R ${pkgs.jitsi-meet}/* .
             for file in *.html **/*.html ; do
               ${pkgs.sd}/bin/sd '<!--#include virtual="(.*)" -->' '{{ include "$1" }}' $file
@@ -390,13 +509,24 @@ in
       };
     };
 
+    services.jitsi-meet.config = recursiveUpdate
+      (mkIf cfg.excalidraw.enable {
+        whiteboard = {
+          enabled = true;
+          collabServerBaseUrl = "https://${cfg.hostName}";
+        };
+      })
+      (mkIf cfg.secureDomain.enable {
+        hosts.anonymousdomain = "guest.${cfg.hostName}";
+      });
+
     services.jitsi-videobridge = mkIf cfg.videobridge.enable {
       enable = true;
       xmppConfigs."localhost" = {
         userName = "jvb";
         domain = "auth.${cfg.hostName}";
         passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
-        mucJids = "jvbbrewery@internal.${cfg.hostName}";
+        mucJids = "jvbbrewery@internal.auth.${cfg.hostName}";
         disableCertificateVerification = true;
       };
     };
@@ -409,17 +539,27 @@ in
       userName = "focus";
       userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret";
       componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
-      bridgeMuc = "jvbbrewery@internal.${cfg.hostName}";
+      bridgeMuc = "jvbbrewery@internal.auth.${cfg.hostName}";
       config = mkMerge [{
         jicofo.xmpp.service.disable-certificate-verification = true;
         jicofo.xmpp.client.disable-certificate-verification = true;
-      #} (lib.mkIf cfg.jibri.enable {
-       } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
-         jicofo.jibri = {
-           brewery-jid = "JibriBrewery@internal.${cfg.hostName}";
-           pending-timeout = "90";
-         };
-      })];
+      }
+        (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
+          jicofo.jibri = {
+            brewery-jid = "JibriBrewery@internal.auth.${cfg.hostName}";
+            pending-timeout = "90";
+          };
+        })
+        (lib.mkIf cfg.secureDomain.enable {
+          jicofo = {
+            authentication = {
+              enabled = "true";
+              type = "XMPP";
+              login-url = cfg.hostName;
+            };
+            xmpp.client.client-proxy = "focus.${cfg.hostName}";
+          };
+        })];
     };
 
     services.jibri = mkIf cfg.jibri.enable {
@@ -430,7 +570,7 @@ in
         xmppDomain = cfg.hostName;
 
         control.muc = {
-          domain = "internal.${cfg.hostName}";
+          domain = "internal.auth.${cfg.hostName}";
           roomName = "JibriBrewery";
           nickname = "jibri";
         };
diff --git a/nixos/modules/services/web-apps/lanraragi.nix b/nixos/modules/services/web-apps/lanraragi.nix
new file mode 100644
index 00000000000..f1ab8b8b4eb
--- /dev/null
+++ b/nixos/modules/services/web-apps/lanraragi.nix
@@ -0,0 +1,100 @@
+{ pkgs, lib, config, ... }:
+
+let
+  cfg = config.services.lanraragi;
+in
+{
+  meta.maintainers = with lib.maintainers; [ tomasajt ];
+
+  options.services = {
+    lanraragi = {
+      enable = lib.mkEnableOption (lib.mdDoc "LANraragi");
+      package = lib.mkPackageOptionMD pkgs "lanraragi" { };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 3000;
+        description = lib.mdDoc "Port for LANraragi's web interface.";
+      };
+
+      passwordFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/lanraragi-password";
+        description = lib.mdDoc ''
+          A file containing the password for LANraragi's admin interface.
+        '';
+      };
+
+      redis = {
+        port = lib.mkOption {
+          type = lib.types.port;
+          default = 6379;
+          description = lib.mdDoc "Port for LANraragi's Redis server.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/run/keys/redis-lanraragi-password";
+          description = lib.mdDoc ''
+            A file containing the password for LANraragi's Redis server.
+          '';
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.redis.servers.lanraragi = {
+      enable = true;
+      port = cfg.redis.port;
+      requirePassFile = cfg.redis.passwordFile;
+    };
+
+    systemd.services.lanraragi = {
+      description = "LANraragi main service";
+      after = [ "network.target" "redis-lanraragi.service" ];
+      requires = [ "redis-lanraragi.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = lib.getExe cfg.package;
+        DynamicUser = true;
+        StateDirectory = "lanraragi";
+        RuntimeDirectory = "lanraragi";
+        LogsDirectory = "lanraragi";
+        Restart = "on-failure";
+        WorkingDirectory = "/var/lib/lanraragi";
+      };
+      environment = {
+        "LRR_TEMP_DIRECTORY" = "/run/lanraragi";
+        "LRR_LOG_DIRECTORY" = "/var/log/lanraragi";
+        "LRR_NETWORK" = "http://*:${toString cfg.port}";
+        "HOME" = "/var/lib/lanraragi";
+      };
+      preStart = ''
+        REDIS_PASS=${lib.optionalString (cfg.redis.passwordFile != null) "$(head -n1 ${cfg.redis.passwordFile})"}
+        cat > lrr.conf <<EOF
+        {
+          redis_address => "127.0.0.1:${toString cfg.redis.port}",
+          redis_password => "$REDIS_PASS",
+          redis_database => "0",
+          redis_database_minion => "1",
+          redis_database_config => "2",
+          redis_database_search => "3",
+        }
+        EOF
+      '' + lib.optionalString (cfg.passwordFile != null) ''
+        PASS_HASH=$(
+          PASS=$(head -n1 ${cfg.passwordFile}) ${cfg.package.perlEnv}/bin/perl -I${cfg.package}/share/lanraragi/lib -e \
+            'use LANraragi::Controller::Config; print LANraragi::Controller::Config::make_password_hash($ENV{PASS})' \
+            2>/dev/null
+        )
+
+        ${lib.getExe pkgs.redis} -h 127.0.0.1 -p ${toString cfg.redis.port} -a "$REDIS_PASS" <<EOF
+          SELECT 2
+          HSET LRR_CONFIG password $PASS_HASH
+        EOF
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
index 20d9dcb7c26..32389f7a59d 100644
--- a/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixos/modules/services/web-apps/lemmy.nix
@@ -146,7 +146,7 @@ in
         ensureDatabases = [ cfg.settings.database.database ];
         ensureUsers = [{
           name = cfg.settings.database.user;
-          ensurePermissions."DATABASE ${cfg.settings.database.database}" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }];
       };
 
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index 2aab97438b7..8686506b1c2 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -17,9 +17,6 @@ let
     WEB_CONCURRENCY = toString cfg.webProcesses;
     MAX_THREADS = toString cfg.webThreads;
 
-    # mastodon-streaming concurrency.
-    STREAMING_CLUSTER_NUM = toString cfg.streamingProcesses;
-
     DB_USER = cfg.database.user;
 
     REDIS_HOST = cfg.redis.host;
@@ -33,13 +30,15 @@ let
     PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system";
     PAPERCLIP_ROOT_URL = "/system";
     ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
-    ES_HOST = cfg.elasticsearch.host;
-    ES_PORT = toString(cfg.elasticsearch.port);
 
     TRUSTED_PROXY_IP = cfg.trustedProxy;
   }
   // lib.optionalAttrs (cfg.database.host != "/run/postgresql" && cfg.database.port != null) { DB_PORT = toString cfg.database.port; }
   // lib.optionalAttrs cfg.smtp.authenticate { SMTP_LOGIN  = cfg.smtp.user; }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_HOST = cfg.elasticsearch.host; }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PORT = toString(cfg.elasticsearch.port); }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PRESET = cfg.elasticsearch.preset; }
+  // lib.optionalAttrs (cfg.elasticsearch.user != null) { ES_USER = cfg.elasticsearch.user; }
   // cfg.extraConfig;
 
   systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@mount" "@obsolete" "@privileged" "@setuid" ];
@@ -141,8 +140,44 @@ let
     })
   ) cfg.sidekiqProcesses;
 
+  streamingUnits = builtins.listToAttrs
+      (map (i: {
+        name = "mastodon-streaming-${toString i}";
+        value = {
+          after = [ "network.target" "mastodon-init-dirs.service" ]
+            ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+            ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+          requires = [ "mastodon-init-dirs.service" ]
+            ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+            ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+          wantedBy = [ "mastodon.target" "mastodon-streaming.target" ];
+          description = "Mastodon streaming ${toString i}";
+          environment = env // { SOCKET = "/run/mastodon-streaming/streaming-${toString i}.socket"; };
+          serviceConfig = {
+            ExecStart = "${cfg.package}/run-streaming.sh";
+            Restart = "always";
+            RestartSec = 20;
+            EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
+            WorkingDirectory = cfg.package;
+            # Runtime directory and mode
+            RuntimeDirectory = "mastodon-streaming";
+            RuntimeDirectoryMode = "0750";
+            # System Call Filtering
+            SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
+          } // cfgService;
+        };
+      })
+      (lib.range 1 cfg.streamingProcesses));
+
 in {
 
+  imports = [
+    (lib.mkRemovedOptionModule
+      [ "services" "mastodon" "streamingPort" ]
+      "Mastodon currently doesn't support streaming via TCP ports. Please open a PR if you need this."
+    )
+  ];
+
   options = {
     services.mastodon = {
       enable = lib.mkEnableOption (lib.mdDoc "Mastodon, a federated social network server");
@@ -191,18 +226,13 @@ in {
         default = "mastodon";
       };
 
-      streamingPort = lib.mkOption {
-        description = lib.mdDoc "TCP port used by the mastodon-streaming service.";
-        type = lib.types.port;
-        default = 55000;
-      };
       streamingProcesses = lib.mkOption {
         description = lib.mdDoc ''
-          Processes used by the mastodon-streaming service.
-          Defaults to the number of CPU cores minus one.
+          Number of processes used by the mastodon-streaming service.
+          Recommended is the amount of your CPU cores minus one.
         '';
-        type = lib.types.nullOr lib.types.int;
-        default = null;
+        type = lib.types.ints.positive;
+        example = 3;
       };
 
       webPort = lib.mkOption {
@@ -485,6 +515,31 @@ in {
           type = lib.types.port;
           default = 9200;
         };
+
+        preset = lib.mkOption {
+          description = lib.mdDoc ''
+            It controls the ElasticSearch indices configuration (number of shards and replica).
+          '';
+          type = lib.types.enum [ "single_node_cluster" "small_cluster" "large_cluster" ];
+          default = "single_node_cluster";
+          example = "large_cluster";
+        };
+
+        user = lib.mkOption {
+          description = lib.mdDoc "Used for optionally authenticating with Elasticsearch.";
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+          example = "elasticsearch-mastodon";
+        };
+
+        passwordFile = lib.mkOption {
+          description = lib.mdDoc ''
+            Path to file containing password for optionally authenticating with Elasticsearch.
+          '';
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/var/lib/mastodon/secrets/elasticsearch-password";
+        };
       };
 
       package = lib.mkOption {
@@ -557,7 +612,7 @@ in {
   config = lib.mkIf cfg.enable (lib.mkMerge [{
     assertions = [
       {
-        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user);
+        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user && cfg.database.user == cfg.database.name);
         message = ''
           For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer
             authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user
@@ -603,6 +658,12 @@ in {
       after = [ "network.target" ];
     };
 
+    systemd.targets.mastodon-streaming = {
+      description = "Target for all Mastodon streaming services";
+      wantedBy = [ "multi-user.target" "mastodon.target" ];
+      after = [ "network.target" ];
+    };
+
     systemd.services.mastodon-init-dirs = {
       script = ''
         umask 077
@@ -631,6 +692,8 @@ in {
         DB_PASS="$(cat ${cfg.database.passwordFile})"
       '' + lib.optionalString cfg.smtp.authenticate ''
         SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})"
+      '' + lib.optionalString (cfg.elasticsearch.passwordFile != null) ''
+        ES_PASS="$(cat ${cfg.elasticsearch.passwordFile})"
       '' + ''
         EOF
       '';
@@ -688,33 +751,6 @@ in {
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service";
     };
 
-    systemd.services.mastodon-streaming = {
-      after = [ "network.target" "mastodon-init-dirs.service" ]
-        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
-        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
-      requires = [ "mastodon-init-dirs.service" ]
-        ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
-        ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
-      wantedBy = [ "mastodon.target" ];
-      description = "Mastodon streaming";
-      environment = env // (if cfg.enableUnixSocket
-        then { SOCKET = "/run/mastodon-streaming/streaming.socket"; }
-        else { PORT = toString(cfg.streamingPort); }
-      );
-      serviceConfig = {
-        ExecStart = "${cfg.package}/run-streaming.sh";
-        Restart = "always";
-        RestartSec = 20;
-        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
-        WorkingDirectory = cfg.package;
-        # Runtime directory and mode
-        RuntimeDirectory = "mastodon-streaming";
-        RuntimeDirectoryMode = "0750";
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
-      } // cfgService;
-    };
-
     systemd.services.mastodon-web = {
       after = [ "network.target" "mastodon-init-dirs.service" ]
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
@@ -780,10 +816,20 @@ in {
         };
 
         locations."/api/v1/streaming/" = {
-          proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-streaming/streaming.socket" else "http://127.0.0.1:${toString(cfg.streamingPort)}/");
+          proxyPass = "http://mastodon-streaming";
           proxyWebsockets = true;
         };
       };
+      upstreams.mastodon-streaming = {
+        extraConfig = ''
+          least_conn;
+        '';
+        servers = builtins.listToAttrs
+          (map (i: {
+            name = "unix:/run/mastodon-streaming/streaming-${toString i}.socket";
+            value = { };
+          }) (lib.range 1 cfg.streamingProcesses));
+      };
     };
 
     services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") {
@@ -799,8 +845,8 @@ in {
       enable = true;
       ensureUsers = [
         {
-          name = cfg.database.user;
-          ensurePermissions."DATABASE ${cfg.database.name}" = "ALL PRIVILEGES";
+          name = cfg.database.name;
+          ensureDBOwnership = true;
         }
       ];
       ensureDatabases = [ cfg.database.name ];
@@ -819,7 +865,7 @@ in {
 
     users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user;
   }
-  { systemd.services = sidekiqUnits; }
+  { systemd.services = lib.mkMerge [ sidekiqUnits streamingUnits ]; }
   ]);
 
   meta.maintainers = with lib.maintainers; [ happy-river erictapen ];
diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix
index 66e5f1695a1..24f3b333184 100644
--- a/nixos/modules/services/web-apps/mattermost.nix
+++ b/nixos/modules/services/web-apps/mattermost.nix
@@ -287,9 +287,9 @@ in
 
       # The systemd service will fail to execute the preStart hook
       # if the WorkingDirectory does not exist
-      system.activationScripts.mattermost = ''
-        mkdir -p "${cfg.statePath}"
-      '';
+      systemd.tmpfiles.rules = [
+        ''d "${cfg.statePath}" -''
+      ];
 
       systemd.services.mattermost = {
         description = "Mattermost chat service";
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index 21c587694c6..ce7bcd94b3f 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -8,7 +8,12 @@ let
   cfg = config.services.mediawiki;
   fpm = config.services.phpfpm.pools.mediawiki;
   user = "mediawiki";
-  group = if cfg.webserver == "apache" then config.services.httpd.group else "mediawiki";
+  group =
+    if cfg.webserver == "apache" then
+      config.services.httpd.group
+    else if cfg.webserver == "nginx" then
+      config.services.nginx.group
+    else "mediawiki";
 
   cacheDir = "/var/cache/mediawiki";
   stateDir = "/var/lib/mediawiki";
@@ -71,7 +76,7 @@ let
       ## For more information on customizing the URLs
       ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
       ## https://www.mediawiki.org/wiki/Manual:Short_URL
-      $wgScriptPath = "";
+      $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}";
 
       ## The protocol and server name to use in fully-qualified URLs
       $wgServer = "${cfg.url}";
@@ -79,6 +84,11 @@ let
       ## The URL path to static resources (images, scripts, etc.)
       $wgResourceBasePath = $wgScriptPath;
 
+      ${lib.optionalString (cfg.webserver == "nginx") ''
+        $wgArticlePath = "/wiki/$1";
+        $wgUsePathInfo = true;
+      ''}
+
       ## The URL path to the logo.  Make sure you change this from the default,
       ## or else you'll overwrite your logo when you upgrade!
       $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
@@ -175,6 +185,7 @@ let
       ${cfg.extraConfig}
   '';
 
+  withTrailingSlash = str: if lib.hasSuffix "/" str then str else "${str}/";
 in
 {
   # interface
@@ -209,8 +220,14 @@ in
 
       url = mkOption {
         type = types.str;
-        default = if cfg.webserver == "apache" then
+        default =
+          if cfg.webserver == "apache" then
             "${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://${cfg.httpd.virtualHost.hostName}"
+          else if cfg.webserver == "nginx" then
+            let
+              hasSSL = host: host.forceSSL || host.addSSL;
+            in
+            "${if hasSSL config.services.nginx.virtualHosts.${cfg.nginx.hostName} then "https" else "http"}://${cfg.nginx.hostName}"
           else
             "http://localhost";
         defaultText = literalExpression ''
@@ -286,7 +303,7 @@ in
       };
 
       webserver = mkOption {
-        type = types.enum [ "apache" "none" ];
+        type = types.enum [ "apache" "none" "nginx" ];
         default = "apache";
         description = lib.mdDoc "Webserver to use.";
       };
@@ -368,6 +385,16 @@ in
         };
       };
 
+      nginx.hostName = mkOption {
+        type = types.str;
+        example = literalExpression ''wiki.example.com'';
+        default = "localhost";
+        description = lib.mdDoc ''
+          The hostname to use for the nginx virtual host.
+          This is used to generate the nginx configuration.
+        '';
+      };
+
       httpd.virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
         example = literalExpression ''
@@ -427,7 +454,7 @@ in
       { assertion = cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres");
         message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'";
       }
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.socket != null;
@@ -459,16 +486,21 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [{
         name = cfg.database.user;
-        ensurePermissions = { "DATABASE \"${cfg.database.name}\"" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
     };
 
     services.phpfpm.pools.mediawiki = {
       inherit user group;
       phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}";
+      # https://www.mediawiki.org/wiki/Compatibility
+      phpPackage = pkgs.php81;
       settings = (if (cfg.webserver == "apache") then {
         "listen.owner" = config.services.httpd.user;
         "listen.group" = config.services.httpd.group;
+      } else if (cfg.webserver == "nginx") then {
+        "listen.owner" = config.services.nginx.user;
+        "listen.group" = config.services.nginx.group;
       } else {
         "listen.owner" = user;
         "listen.group" = group;
@@ -503,6 +535,58 @@ in
         }
       ];
     };
+    # inspired by https://www.mediawiki.org/wiki/Manual:Short_URL/Nginx
+    services.nginx = lib.mkIf (cfg.webserver == "nginx") {
+      enable = true;
+      virtualHosts.${config.services.mediawiki.nginx.hostName} = {
+        root = "${pkg}/share/mediawiki";
+        locations = {
+          "~ ^/w/(index|load|api|thumb|opensearch_desc|rest|img_auth)\\.php$".extraConfig = ''
+            rewrite ^/w/(.*) /$1 break;
+            include ${config.services.nginx.package}/conf/fastcgi_params;
+            fastcgi_index index.php;
+            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+            fastcgi_pass unix:${config.services.phpfpm.pools.mediawiki.socket};
+          '';
+          "/w/images/".alias = withTrailingSlash cfg.uploadsDir;
+          # Deny access to deleted images folder
+          "/w/images/deleted".extraConfig = ''
+            deny all;
+          '';
+          # MediaWiki assets (usually images)
+          "~ ^/w/resources/(assets|lib|src)".extraConfig = ''
+            rewrite ^/w(/.*) $1 break;
+            add_header Cache-Control "public";
+            expires 7d;
+          '';
+          # Assets, scripts and styles from skins and extensions
+          "~ ^/w/(skins|extensions)/.+\\.(css|js|gif|jpg|jpeg|png|svg|wasm|ttf|woff|woff2)$".extraConfig = ''
+            rewrite ^/w(/.*) $1 break;
+            add_header Cache-Control "public";
+            expires 7d;
+          '';
+
+          # Handling for Mediawiki REST API, see [[mw:API:REST_API]]
+          "/w/rest.php/".tryFiles = "$uri $uri/ /w/rest.php?$query_string";
+
+          # Handling for the article path (pretty URLs)
+          "/wiki/".extraConfig = ''
+            rewrite ^/wiki/(?<pagename>.*)$ /w/index.php;
+          '';
+
+          # Explicit access to the root website, redirect to main page (adapt as needed)
+          "= /".extraConfig = ''
+            return 301 /wiki/Main_Page;
+          '';
+
+          # Every other entry point will be disallowed.
+          # Add specific rules for other entry points/images as needed above this
+          "/".extraConfig = ''
+             return 404;
+          '';
+        };
+      };
+    };
 
     systemd.tmpfiles.rules = [
       "d '${stateDir}' 0750 ${user} ${group} - -"
diff --git a/nixos/modules/services/web-apps/meme-bingo-web.nix b/nixos/modules/services/web-apps/meme-bingo-web.nix
index cb864321ef2..652dc884025 100644
--- a/nixos/modules/services/web-apps/meme-bingo-web.nix
+++ b/nixos/modules/services/web-apps/meme-bingo-web.nix
@@ -8,9 +8,9 @@ in {
   options = {
     services.meme-bingo-web = {
       enable = mkEnableOption (mdDoc ''
-        A web app for the meme bingo, rendered entirely on the web server and made interactive with forms.
+        a web app for the meme bingo, rendered entirely on the web server and made interactive with forms.
 
-        Note: The application's author suppose to run meme-bingo-web behind a reverse proxy for SSL and HTTP/3.
+        Note: The application's author suppose to run meme-bingo-web behind a reverse proxy for SSL and HTTP/3
       '');
 
       package = mkOption {
diff --git a/nixos/modules/services/web-apps/microbin.nix b/nixos/modules/services/web-apps/microbin.nix
new file mode 100644
index 00000000000..233bfac6e69
--- /dev/null
+++ b/nixos/modules/services/web-apps/microbin.nix
@@ -0,0 +1,93 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.microbin;
+in
+{
+  options.services.microbin = {
+    enable = lib.mkEnableOption (lib.mdDoc "MicroBin is a super tiny, feature rich, configurable paste bin web application");
+
+    package = lib.mkPackageOption pkgs "microbin" { };
+
+    settings = lib.mkOption {
+      type = lib.types.submodule { freeformType = with lib.types; attrsOf (oneOf [ bool int str ]); };
+      default = { };
+      example = {
+        MICROBIN_PORT = 8080;
+        MICROBIN_HIDE_LOGO = false;
+      };
+      description = lib.mdDoc ''
+        Additional configuration for MicroBin, see
+        <https://microbin.eu/docs/installation-and-configuration/configuration/>
+        for supported values.
+
+        For secrets use passwordFile option instead.
+      '';
+    };
+
+    dataDir = lib.mkOption {
+      type = lib.types.str;
+      default = "/var/lib/microbin";
+      description = lib.mdDoc "Default data folder for MicroBin.";
+    };
+
+    passwordFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      example = "/run/secrets/microbin.env";
+      description = lib.mdDoc ''
+        Path to file containing environment variables.
+        Useful for passing down secrets.
+        Variables that can be considered secrets are:
+         - MICROBIN_BASIC_AUTH_USERNAME
+         - MICROBIN_BASIC_AUTH_PASSWORD
+         - MICROBIN_ADMIN_USERNAME
+         - MICROBIN_ADMIN_PASSWORD
+         - MICROBIN_UPLOADER_PASSWORD
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.microbin.settings = with lib; {
+      MICROBIN_BIND = mkDefault "0.0.0.0";
+      MICROBIN_DISABLE_TELEMETRY = mkDefault true;
+      MICROBIN_LIST_SERVER = mkDefault false;
+      MICROBIN_PORT = mkDefault "8080";
+    };
+
+    systemd.services.microbin = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = lib.mapAttrs (_: v: if lib.isBool v then lib.boolToString v else toString v) cfg.settings;
+      serviceConfig = {
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        EnvironmentFile = lib.optional (cfg.passwordFile != null) cfg.passwordFile;
+        ExecStart = "${cfg.package}/bin/microbin";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ReadWritePaths = cfg.dataDir;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        StateDirectory = "microbin";
+        SystemCallArchitectures = [ "native" ];
+        SystemCallFilter = [ "@system-service" ];
+        WorkingDirectory = cfg.dataDir;
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ surfaceflinger ];
+}
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 3374c746ad3..5c8c93c13c4 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -6,13 +6,10 @@ let
 
   defaultAddress = "localhost:8080";
 
-  dbUser = "miniflux";
-  dbName = "miniflux";
-
   pgbin = "${config.services.postgresql.package}/bin";
   preStart = pkgs.writeScript "miniflux-pre-start" ''
     #!${pkgs.runtimeShell}
-    ${pgbin}/psql "${dbName}" -c "CREATE EXTENSION IF NOT EXISTS hstore"
+    ${pgbin}/psql "miniflux" -c "CREATE EXTENSION IF NOT EXISTS hstore"
   '';
 in
 
@@ -62,7 +59,7 @@ in
 
     services.miniflux.config =  {
       LISTEN_ADDR = mkDefault defaultAddress;
-      DATABASE_URL = "user=${dbUser} host=/run/postgresql dbname=${dbName}";
+      DATABASE_URL = "user=miniflux host=/run/postgresql dbname=miniflux";
       RUN_MIGRATIONS = "1";
       CREATE_ADMIN = "1";
     };
@@ -70,12 +67,10 @@ in
     services.postgresql = {
       enable = true;
       ensureUsers = [ {
-        name = dbUser;
-        ensurePermissions = {
-          "DATABASE ${dbName}" = "ALL PRIVILEGES";
-        };
+        name = "miniflux";
+        ensureDBOwnership = true;
       } ];
-      ensureDatabases = [ dbName ];
+      ensureDatabases = [ "miniflux" ];
     };
 
     systemd.services.miniflux-dbsetup = {
@@ -97,7 +92,7 @@ in
 
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/miniflux";
-        User = dbUser;
+        User = "miniflux";
         DynamicUser = true;
         RuntimeDirectory = "miniflux";
         RuntimeDirectoryMode = "0700";
diff --git a/nixos/modules/services/web-apps/mobilizon.nix b/nixos/modules/services/web-apps/mobilizon.nix
index e9264a38f0e..bb4319b51a2 100644
--- a/nixos/modules/services/web-apps/mobilizon.nix
+++ b/nixos/modules/services/web-apps/mobilizon.nix
@@ -8,7 +8,7 @@ let
   user = "mobilizon";
   group = "mobilizon";
 
-  settingsFormat = pkgs.formats.elixirConf { elixir = pkgs.elixir_1_14; };
+  settingsFormat = pkgs.formats.elixirConf { elixir = cfg.package.elixirPackage; };
 
   configFile = settingsFormat.generate "mobilizon-config.exs" cfg.settings;
 
@@ -309,7 +309,7 @@ in
           genCookie = "IO.puts(Base.encode32(:crypto.strong_rand_bytes(32)))";
 
           evalElixir = str: ''
-            ${pkgs.elixir_1_14}/bin/elixir --eval '${str}'
+            ${cfg.package.elixirPackage}/bin/elixir --eval '${str}'
           '';
         in
         ''
@@ -347,12 +347,18 @@ in
 
       # Taken from here:
       # https://framagit.org/framasoft/mobilizon/-/blob/1.1.0/priv/templates/setup_db.eex
+      # TODO(to maintainers of mobilizon): the owner database alteration is necessary
+      # as PostgreSQL 15 changed their behaviors w.r.t. to privileges.
+      # See https://github.com/NixOS/nixpkgs/issues/216989 to get rid
+      # of that workaround.
       script =
         ''
           psql "${repoSettings.database}" -c "\
             CREATE EXTENSION IF NOT EXISTS postgis; \
             CREATE EXTENSION IF NOT EXISTS pg_trgm; \
             CREATE EXTENSION IF NOT EXISTS unaccent;"
+          psql -tAc 'ALTER DATABASE "${repoSettings.database}" OWNER TO "${dbUser}";'
+
         '';
 
       serviceConfig = {
@@ -372,9 +378,10 @@ in
       ensureUsers = [
         {
           name = dbUser;
-          ensurePermissions = {
-            "DATABASE \"${repoSettings.database}\"" = "ALL PRIVILEGES";
-          };
+          # Given that `dbUser` is potentially arbitrarily custom, we will perform
+          # manual fixups in mobilizon-postgres.
+          # TODO(to maintainers of mobilizon): Feel free to simplify your setup by using `ensureDBOwnership`.
+          ensureDBOwnership = false;
         }
       ];
       extraPlugins = with postgresql.pkgs; [ postgis ];
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
index b617e9a5937..04ae6bd7f17 100644
--- a/nixos/modules/services/web-apps/moodle.nix
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -194,7 +194,7 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
         message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -220,7 +220,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
index 6d89ffc2a7b..3b9434e3d34 100644
--- a/nixos/modules/services/web-apps/netbox.nix
+++ b/nixos/modules/services/web-apps/netbox.nix
@@ -74,9 +74,18 @@ in {
 
     package = lib.mkOption {
       type = lib.types.package;
-      default = if lib.versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+      default =
+        if lib.versionAtLeast config.system.stateVersion "23.11"
+        then pkgs.netbox_3_6
+        else if lib.versionAtLeast config.system.stateVersion "23.05"
+        then pkgs.netbox_3_5
+        else pkgs.netbox_3_3;
       defaultText = lib.literalExpression ''
-        if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+        if lib.versionAtLeast config.system.stateVersion "23.11"
+        then pkgs.netbox_3_6
+        else if lib.versionAtLeast config.system.stateVersion "23.05"
+        then pkgs.netbox_3_5
+        else pkgs.netbox_3_3;
       '';
       description = lib.mdDoc ''
         NetBox package to use.
@@ -248,9 +257,7 @@ in {
       ensureUsers = [
         {
           name = "netbox";
-          ensurePermissions = {
-            "DATABASE netbox" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md
index cbd7b5b3d06..ecc7f380592 100644
--- a/nixos/modules/services/web-apps/nextcloud.md
+++ b/nixos/modules/services/web-apps/nextcloud.md
@@ -49,7 +49,7 @@ used by the imperative installer and all values are written to an additional fil
 to ensure that changes can be applied by changing the module's options.
 
 In case the application serves multiple domains (those are checked with
-[`$_SERVER['HTTP_HOST']`](http://php.net/manual/en/reserved.variables.server.php))
+[`$_SERVER['HTTP_HOST']`](https://www.php.net/manual/en/reserved.variables.server.php))
 it's needed to add them to
 [`services.nextcloud.config.extraTrustedDomains`](#opt-services.nextcloud.config.extraTrustedDomains).
 
@@ -119,13 +119,7 @@ Auto updates for Nextcloud apps can be enabled using
   - **Server-side encryption.**
     Nextcloud supports [server-side encryption (SSE)](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html).
     This is not an end-to-end encryption, but can be used to encrypt files that will be persisted
-    to external storage such as S3. Please note that this won't work anymore when using OpenSSL 3
-    for PHP's openssl extension and **Nextcloud 25 or older** because this is implemented using the
-    legacy cipher RC4. For Nextcloud26 this isn't relevant anymore, because Nextcloud has an RC4 implementation
-    written in native PHP and thus doesn't need `ext-openssl` for that anymore.
-    If [](#opt-system.stateVersion) is *above* `22.05`,
-    this is disabled by default. To turn it on again and for further information please refer to
-    [](#opt-services.nextcloud.enableBrokenCiphersForSSE).
+    to external storage such as S3.
 
 ## Using an alternative webserver as reverse-proxy (e.g. `httpd`) {#module-services-nextcloud-httpd}
 
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index e0a7e7d4859..f1ac3770d40 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -27,13 +27,7 @@ let
 
   phpPackage = cfg.phpPackage.buildEnv {
     extensions = { enabled, all }:
-      (with all;
-        # disable default openssl extension
-        (lib.filter (e: e.pname != "php-openssl") enabled)
-        # use OpenSSL 1.1 for RC4 Nextcloud encryption if user
-        # has acknowledged the brokenness of the ciphers (RC4).
-        # TODO: remove when https://github.com/nextcloud/server/issues/32003 is fixed.
-        ++ (if cfg.enableBrokenCiphersForSSE then [ cfg.phpPackage.extensions.openssl-legacy ] else [ cfg.phpPackage.extensions.openssl ])
+      (with all; enabled
         ++ optional cfg.enableImagemagick imagick
         # Optionally enabled depending on caching settings
         ++ optional cfg.caching.apcu apcu
@@ -66,6 +60,9 @@ let
   mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
   pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
 
+  # https://github.com/nextcloud/documentation/pull/11179
+  ocmProviderIsNotAStaticDirAnymore = versionAtLeast cfg.package.version "27.1.2";
+
 in {
 
   imports = [
@@ -87,6 +84,10 @@ in {
       Further details about this can be found in the `Nextcloud`-section of the NixOS-manual
       (which can be opened e.g. by running `nixos-help`).
     '')
+    (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] ''
+      This option has no effect since there's no supported Nextcloud version packaged here
+      using OpenSSL for RC4 SSE.
+    '')
     (mkRemovedOptionModule [ "services" "nextcloud" "disableImagemagick" ] ''
       Use services.nextcloud.enableImagemagick instead.
     '')
@@ -95,39 +96,6 @@ in {
   options.services.nextcloud = {
     enable = mkEnableOption (lib.mdDoc "nextcloud");
 
-    enableBrokenCiphersForSSE = mkOption {
-      type = types.bool;
-      default = versionOlder stateVersion "22.11";
-      defaultText = literalExpression "versionOlder system.stateVersion \"22.11\"";
-      description = lib.mdDoc ''
-        This option enables using the OpenSSL PHP extension linked against OpenSSL 1.1
-        rather than latest OpenSSL (≥ 3), this is not recommended unless you need
-        it for server-side encryption (SSE). SSE uses the legacy RC4 cipher which is
-        considered broken for several years now. See also [RFC7465](https://datatracker.ietf.org/doc/html/rfc7465).
-
-        This cipher has been disabled in OpenSSL ≥ 3 and requires
-        a specific legacy profile to re-enable it.
-
-        If you deploy Nextcloud using OpenSSL ≥ 3 for PHP and have
-        server-side encryption configured, you will not be able to access
-        your files anymore. Enabling this option can restore access to your files.
-        Upon testing we didn't encounter any data corruption when turning
-        this on and off again, but this cannot be guaranteed for
-        each Nextcloud installation.
-
-        It is `true` by default for systems with a [](#opt-system.stateVersion) below
-        `22.11` to make sure that existing installations won't break on update. On newer
-        NixOS systems you have to explicitly enable it on your own.
-
-        Please note that this only provides additional value when using
-        external storage such as S3 since it's not an end-to-end encryption.
-        If this is not the case,
-        it is advised to [disable server-side encryption](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption) and set this to `false`.
-
-        In the future, Nextcloud may move to AES-256-GCM, by then,
-        this option will be removed.
-      '';
-    };
     hostName = mkOption {
       type = types.str;
       description = lib.mdDoc "FQDN for the nextcloud instance.";
@@ -225,7 +193,7 @@ in {
     package = mkOption {
       type = types.package;
       description = lib.mdDoc "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud25" "nextcloud26" "nextcloud27" ];
+      relatedPackages = [ "nextcloud26" "nextcloud27" ];
     };
     phpPackage = mkOption {
       type = types.package;
@@ -740,28 +708,7 @@ in {
         '')
         ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
         ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
-        ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11"))
-        ++ (optional cfg.enableBrokenCiphersForSSE ''
-          You're using PHP's openssl extension built against OpenSSL 1.1 for Nextcloud.
-          This is only necessary if you're using Nextcloud's server-side encryption.
-          Please keep in mind that it's using the broken RC4 cipher.
-
-          If you don't use that feature, you can switch to OpenSSL 3 and get
-          rid of this warning by declaring
-
-            services.nextcloud.enableBrokenCiphersForSSE = false;
-
-          If you need to use server-side encryption you can ignore this warning.
-          Otherwise you'd have to disable server-side encryption first in order
-          to be able to safely disable this option and get rid of this warning.
-          See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption> on how to achieve this.
-
-          For more context, here is the implementing pull request: https://github.com/NixOS/nixpkgs/pull/198470
-        '')
-        ++ (optional (cfg.enableBrokenCiphersForSSE && versionAtLeast cfg.package.version "26") ''
-          Nextcloud26 supports RC4 without requiring legacy OpenSSL, so
-          `services.nextcloud.enableBrokenCiphersForSSE` can be set to `false`.
-        '');
+        ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11"));
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -1095,7 +1042,7 @@ in {
         ensureDatabases = [ cfg.config.dbname ];
         ensureUsers = [{
           name = cfg.config.dbuser;
-          ensurePermissions = { "DATABASE ${cfg.config.dbname}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }];
       };
 
@@ -1136,10 +1083,6 @@ in {
               }
             '';
           };
-          "/" = {
-            priority = 900;
-            extraConfig = "rewrite ^ /index.php;";
-          };
           "~ ^/store-apps" = {
             priority = 201;
             extraConfig = "root ${cfg.home};";
@@ -1164,15 +1107,23 @@ in {
               try_files $uri $uri/ =404;
             '';
           };
-          "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)".extraConfig = ''
-            return 404;
-          '';
-          "~ ^/(?:\\.(?!well-known)|autotest|occ|issue|indie|db_|console)".extraConfig = ''
-            return 404;
-          '';
-          "~ ^\\/(?:index|remote|public|cron|core\\/ajax\\/update|status|ocs\\/v[12]|updater\\/.+|oc[ms]-provider\\/.+|.+\\/richdocumentscode\\/proxy)\\.php(?:$|\\/)" = {
+          "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = {
+            priority = 450;
+            extraConfig = ''
+              return 404;
+            '';
+          };
+          "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
+            priority = 450;
+            extraConfig = ''
+              return 404;
+            '';
+          };
+          "~ \\.php(?:$|/)" = {
             priority = 500;
             extraConfig = ''
+              # legacy support (i.e. static files and directories in cfg.package)
+              rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[s${optionalString (!ocmProviderIsNotAStaticDirAnymore) "m"}]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;
               include ${config.services.nginx.package}/conf/fastcgi.conf;
               fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
               set $path_info $fastcgi_path_info;
@@ -1188,19 +1139,30 @@ in {
               fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s;
             '';
           };
-          "~ \\.(?:css|js|woff2?|svg|gif|map)$".extraConfig = ''
+          "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm)$".extraConfig = ''
             try_files $uri /index.php$request_uri;
             expires 6M;
             access_log off;
+            location ~ \.wasm$ {
+              default_type application/wasm;
+            }
           '';
-          "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = ''
+          "~ ^\\/(?:updater|ocs-provider${optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"})(?:$|\\/)".extraConfig = ''
             try_files $uri/ =404;
             index index.php;
           '';
-          "~ \\.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$".extraConfig = ''
-            try_files $uri /index.php$request_uri;
-            access_log off;
-          '';
+          "/remote" = {
+            priority = 1500;
+            extraConfig = ''
+              return 301 /remote.php$request_uri;
+            '';
+          };
+          "/" = {
+            priority = 1600;
+            extraConfig = ''
+              try_files $uri $uri/ /index.php$request_uri;
+            '';
+          };
         };
         extraConfig = ''
           index index.php index.html /index.php$request_uri;
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
index 3494f2fa21f..f958566b91f 100644
--- a/nixos/modules/services/web-apps/onlyoffice.nix
+++ b/nixos/modules/services/web-apps/onlyoffice.nix
@@ -198,7 +198,7 @@ in
         ensureDatabases = [ "onlyoffice" ];
         ensureUsers = [{
           name = "onlyoffice";
-          ensurePermissions = { "DATABASE \"onlyoffice\"" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }];
       };
     };
diff --git a/nixos/modules/services/web-apps/outline.nix b/nixos/modules/services/web-apps/outline.nix
index 1d8298963e6..d97b45d6241 100644
--- a/nixos/modules/services/web-apps/outline.nix
+++ b/nixos/modules/services/web-apps/outline.nix
@@ -117,13 +117,14 @@ in
     storage = lib.mkOption {
       description = lib.mdDoc ''
         To support uploading of images for avatars and document attachments an
-        s3-compatible storage must be provided. AWS S3 is recommended for
+        s3-compatible storage can be provided. AWS S3 is recommended for
         redundancy however if you want to keep all file storage local an
         alternative such as [minio](https://github.com/minio/minio)
         can be used.
+        Local filesystem storage can also be used.
 
-        A more detailed guide on setting up S3 is available
-        [here](https://wiki.generaloutline.com/share/125de1cc-9ff6-424b-8415-0d58c809a40f).
+        A more detailed guide on setting up storage is available
+        [here](https://docs.getoutline.com/s/hosting/doc/file-storage-N4M0T6Ypu7).
       '';
       example = lib.literalExpression ''
         {
@@ -136,6 +137,19 @@ in
       '';
       type = lib.types.submodule {
         options = {
+          storageType = lib.mkOption {
+            type = lib.types.enum [ "local" "s3" ];
+            description = lib.mdDoc "File storage type, it can be local or s3.";
+            default = "s3";
+          };
+          localRootDir = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc ''
+              If `storageType` is `local`, this sets the parent directory
+              under which all attachments/images go.
+            '';
+            default = "/var/lib/outline/data";
+          };
           accessKey = lib.mkOption {
             type = lib.types.str;
             description = lib.mdDoc "S3 access key.";
@@ -557,14 +571,17 @@ in
     systemd.tmpfiles.rules = [
       "f ${cfg.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
       "f ${cfg.utilsSecretFile} 0600 ${cfg.user} ${cfg.group} -"
-      "f ${cfg.storage.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
+      (if (cfg.storage.storageType == "s3") then
+        "f ${cfg.storage.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
+      else
+        "d ${cfg.storage.localRootDir} 0700 ${cfg.user} ${cfg.group} - -")
     ];
 
     services.postgresql = lib.mkIf (cfg.databaseUrl == "local") {
       enable = true;
       ensureUsers = [{
         name = "outline";
-        ensurePermissions."DATABASE outline" = "ALL PRIVILEGES";
+        ensureDBOwnership = true;
       }];
       ensureDatabases = [ "outline" ];
     };
@@ -599,14 +616,6 @@ in
           URL = cfg.publicUrl;
           PORT = builtins.toString cfg.port;
 
-          AWS_ACCESS_KEY_ID = cfg.storage.accessKey;
-          AWS_REGION = cfg.storage.region;
-          AWS_S3_UPLOAD_BUCKET_URL = cfg.storage.uploadBucketUrl;
-          AWS_S3_UPLOAD_BUCKET_NAME = cfg.storage.uploadBucketName;
-          AWS_S3_UPLOAD_MAX_SIZE = builtins.toString cfg.storage.uploadMaxSize;
-          AWS_S3_FORCE_PATH_STYLE = builtins.toString cfg.storage.forcePathStyle;
-          AWS_S3_ACL = cfg.storage.acl;
-
           CDN_URL = cfg.cdnUrl;
           FORCE_HTTPS = builtins.toString cfg.forceHttps;
           ENABLE_UPDATES = builtins.toString cfg.enableUpdateCheck;
@@ -622,8 +631,21 @@ in
           RATE_LIMITER_ENABLED = builtins.toString cfg.rateLimiter.enable;
           RATE_LIMITER_REQUESTS = builtins.toString cfg.rateLimiter.requests;
           RATE_LIMITER_DURATION_WINDOW = builtins.toString cfg.rateLimiter.durationWindow;
+
+          FILE_STORAGE = cfg.storage.storageType;
+          FILE_STORAGE_UPLOAD_MAX_SIZE = builtins.toString cfg.storage.uploadMaxSize;
+          FILE_STORAGE_LOCAL_ROOT_DIR = cfg.storage.localRootDir;
         }
 
+        (lib.mkIf (cfg.storage.storageType == "s3") {
+          AWS_ACCESS_KEY_ID = cfg.storage.accessKey;
+          AWS_REGION = cfg.storage.region;
+          AWS_S3_UPLOAD_BUCKET_URL = cfg.storage.uploadBucketUrl;
+          AWS_S3_UPLOAD_BUCKET_NAME = cfg.storage.uploadBucketName;
+          AWS_S3_FORCE_PATH_STYLE = builtins.toString cfg.storage.forcePathStyle;
+          AWS_S3_ACL = cfg.storage.acl;
+        })
+
         (lib.mkIf (cfg.slackAuthentication != null) {
           SLACK_CLIENT_ID = cfg.slackAuthentication.clientId;
         })
@@ -676,7 +698,9 @@ in
       script = ''
         export SECRET_KEY="$(head -n1 ${lib.escapeShellArg cfg.secretKeyFile})"
         export UTILS_SECRET="$(head -n1 ${lib.escapeShellArg cfg.utilsSecretFile})"
-        export AWS_SECRET_ACCESS_KEY="$(head -n1 ${lib.escapeShellArg cfg.storage.secretKeyFile})"
+        ${lib.optionalString (cfg.storage.storageType == "s3") ''
+          export AWS_SECRET_ACCESS_KEY="$(head -n1 ${lib.escapeShellArg cfg.storage.secretKeyFile})"
+        ''}
         ${lib.optionalString (cfg.slackAuthentication != null) ''
           export SLACK_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.slackAuthentication.secretFile})"
         ''}
diff --git a/nixos/modules/services/web-apps/peering-manager.nix b/nixos/modules/services/web-apps/peering-manager.nix
index 641a3644614..d6f6077268d 100644
--- a/nixos/modules/services/web-apps/peering-manager.nix
+++ b/nixos/modules/services/web-apps/peering-manager.nix
@@ -2,40 +2,15 @@
 
 let
   cfg = config.services.peering-manager;
-  configFile = pkgs.writeTextFile {
-    name = "configuration.py";
-    text = ''
-      ALLOWED_HOSTS = ['*']
-      DATABASE = {
-        'NAME': 'peering-manager',
-        'USER': 'peering-manager',
-        'HOST': '/run/postgresql',
-      }
-
-      # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
-      # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
-      # to use two separate database IDs.
-      REDIS = {
-        'tasks': {
-          'UNIX_SOCKET_PATH': '${config.services.redis.servers.peering-manager.unixSocket}',
-          'DATABASE': 0,
-        },
-        'caching': {
-          'UNIX_SOCKET_PATH': '${config.services.redis.servers.peering-manager.unixSocket}',
-          'DATABASE': 1,
-        }
-      }
-
-      with open("${cfg.secretKeyFile}", "r") as file:
-        SECRET_KEY = file.readline()
-    '' + lib.optionalString (cfg.peeringdbApiKeyFile != null) ''
-      with open("${cfg.peeringdbApiKeyFile}", "r") as file:
-        PEERINGDB_API_KEY = file.readline()
-    '' + ''
 
-      ${cfg.extraConfig}
-    '';
+  pythonFmt = pkgs.formats.pythonVars {};
+  settingsFile = pythonFmt.generate "peering-manager-settings.py" cfg.settings;
+  extraConfigFile = pkgs.writeTextFile {
+    name = "peering-manager-extraConfig.py";
+    text = cfg.extraConfig;
   };
+  configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ];
+
   pkg = (pkgs.peering-manager.overrideAttrs (old: {
     postInstall = ''
       ln -s ${configFile} $out/opt/peering-manager/peering_manager/configuration.py
@@ -60,7 +35,15 @@ in {
         Enable Peering Manager.
 
         This module requires a reverse proxy that serves `/static` separately.
-        See this [example](https://github.com/peering-manager-community/peering-manager/blob/develop/contrib/nginx.conf/) on how to configure this.
+        See this [example](https://github.com/peering-manager/contrib/blob/main/nginx.conf on how to configure this.
+      '';
+    };
+
+    enableScheduledTasks = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Set up [scheduled tasks](https://peering-manager.readthedocs.io/en/stable/setup/8-scheduled-tasks/)
       '';
     };
 
@@ -106,6 +89,30 @@ in {
       '';
     };
 
+    settings = lib.mkOption {
+      description = lib.mdDoc ''
+        Configuration options to set in `configuration.py`.
+        See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
+      '';
+
+      default = { };
+
+      type = lib.types.submodule {
+        freeformType = pythonFmt.type;
+
+        options = {
+          ALLOWED_HOSTS = lib.mkOption {
+            type = with lib.types; listOf str;
+            default = ["*"];
+            description = lib.mdDoc ''
+              A list of valid fully-qualified domain names (FQDNs) and/or IP
+              addresses that can be used to reach the peering manager service.
+            '';
+          };
+        };
+      };
+    };
+
     extraConfig = mkOption {
       type = types.lines;
       default = "";
@@ -135,7 +142,39 @@ in {
   };
 
   config = lib.mkIf cfg.enable {
-    services.peering-manager.plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+    services.peering-manager = {
+      settings = {
+        DATABASE = {
+          NAME = "peering-manager";
+          USER = "peering-manager";
+          HOST = "/run/postgresql";
+        };
+
+        # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
+        # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
+        # to use two separate database IDs.
+        REDIS = {
+          tasks = {
+            UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket;
+            DATABASE = 0;
+          };
+          caching = {
+            UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket;
+            DATABASE = 1;
+          };
+        };
+      };
+
+      extraConfig = ''
+        with open("${cfg.secretKeyFile}", "r") as file:
+          SECRET_KEY = file.readline()
+      '' + lib.optionalString (cfg.peeringdbApiKeyFile != null) ''
+        with open("${cfg.peeringdbApiKeyFile}", "r") as file:
+          PEERINGDB_API_KEY = file.readline()
+      '';
+
+      plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+    };
 
     system.build.peeringManagerPkg = pkg;
 
@@ -147,9 +186,7 @@ in {
       ensureUsers = [
         {
           name = "peering-manager";
-          ensurePermissions = {
-            "DATABASE \"peering-manager\"" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
@@ -163,32 +200,30 @@ in {
     };
 
     systemd.services = let
-      defaultServiceConfig = {
-        WorkingDirectory = "/var/lib/peering-manager";
-        User = "peering-manager";
-        Group = "peering-manager";
-        StateDirectory = "peering-manager";
-        StateDirectoryMode = "0750";
-        Restart = "on-failure";
+      defaults = {
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+        serviceConfig = {
+          WorkingDirectory = "/var/lib/peering-manager";
+          User = "peering-manager";
+          Group = "peering-manager";
+          StateDirectory = "peering-manager";
+          StateDirectoryMode = "0750";
+          Restart = "on-failure";
+        };
       };
     in {
-      peering-manager-migration = {
+      peering-manager-migration = lib.recursiveUpdate defaults {
         description = "Peering Manager migrations";
         wantedBy = [ "peering-manager.target" ];
-
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
-        };
-
-        serviceConfig = defaultServiceConfig // {
+        serviceConfig = {
           Type = "oneshot";
-          ExecStart = ''
-            ${pkg}/bin/peering-manager migrate
-          '';
+          ExecStart = "${pkg}/bin/peering-manager migrate";
         };
       };
 
-      peering-manager = {
+      peering-manager = lib.recursiveUpdate defaults {
         description = "Peering Manager WSGI Service";
         wantedBy = [ "peering-manager.target" ];
         after = [ "peering-manager-migration.service" ];
@@ -197,11 +232,7 @@ in {
           ${pkg}/bin/peering-manager remove_stale_contenttypes --no-input
         '';
 
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
-        };
-
-        serviceConfig = defaultServiceConfig // {
+        serviceConfig = {
           ExecStart = ''
             ${pkg.python.pkgs.gunicorn}/bin/gunicorn peering_manager.wsgi \
               --bind ${cfg.listenAddress}:${toString cfg.port} \
@@ -210,45 +241,92 @@ in {
         };
       };
 
-      peering-manager-rq = {
+      peering-manager-rq = lib.recursiveUpdate defaults {
         description = "Peering Manager Request Queue Worker";
         wantedBy = [ "peering-manager.target" ];
         after = [ "peering-manager.service" ];
+        serviceConfig.ExecStart = "${pkg}/bin/peering-manager rqworker high default low";
+      };
 
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
+      peering-manager-housekeeping = lib.recursiveUpdate defaults {
+        description = "Peering Manager housekeeping job";
+        after = [ "peering-manager.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkg}/bin/peering-manager housekeeping";
         };
+      };
 
-        serviceConfig = defaultServiceConfig // {
-          ExecStart = ''
-            ${pkg}/bin/peering-manager rqworker high default low
-          '';
+      peering-manager-peeringdb-sync = lib.recursiveUpdate defaults {
+        description = "PeeringDB sync";
+        after = [ "peering-manager.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkg}/bin/peering-manager peeringdb_sync";
         };
       };
 
-      peering-manager-housekeeping = {
-        description = "Peering Manager housekeeping job";
+      peering-manager-prefix-fetch = lib.recursiveUpdate defaults {
+        description = "Fetch IRR AS-SET prefixes";
         after = [ "peering-manager.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkg}/bin/peering-manager grab_prefixes";
+        };
+      };
 
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
+      peering-manager-configuration-deployment = lib.recursiveUpdate defaults {
+        description = "Push configuration to routers";
+        after = [ "peering-manager.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkg}/bin/peering-manager configure_routers";
         };
+      };
 
-        serviceConfig = defaultServiceConfig // {
+      peering-manager-session-poll = lib.recursiveUpdate defaults {
+        description = "Poll peering sessions from routers";
+        after = [ "peering-manager.service" ];
+        serviceConfig = {
           Type = "oneshot";
-          ExecStart = ''
-            ${pkg}/bin/peering-manager housekeeping
-          '';
+          ExecStart = "${pkg}/bin/peering-manager poll_bgp_sessions --all";
         };
       };
     };
 
-    systemd.timers.peering-manager-housekeeping = {
-      description = "Run Peering Manager housekeeping job";
-      wantedBy = [ "timers.target" ];
+    systemd.timers = {
+      peering-manager-housekeeping = {
+        description = "Run Peering Manager housekeeping job";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "daily";
+      };
+
+      peering-manager-peeringdb-sync = {
+        enable = lib.mkDefault cfg.enableScheduledTasks;
+        description = "Sync PeeringDB at 2:30";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "02:30:00";
+      };
+
+      peering-manager-prefix-fetch = {
+        enable = lib.mkDefault cfg.enableScheduledTasks;
+        description = "Fetch IRR AS-SET prefixes at 4:30";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "04:30:00";
+      };
+
+      peering-manager-configuration-deployment = {
+        enable = lib.mkDefault cfg.enableScheduledTasks;
+        description = "Push router configuration every hour 5 minutes before full hour";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "*:55:00";
+      };
 
-      timerConfig = {
-        OnCalendar = "daily";
+      peering-manager-session-poll = {
+        enable = lib.mkDefault cfg.enableScheduledTasks;
+        description = "Poll peering sessions from routers every hour";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "*:00:00";
       };
     };
 
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index 17e170c33de..a2246761141 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -352,6 +352,7 @@ in {
         };
         storage = {
           tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
+          tmp_persistent = lib.mkDefault "/var/lib/peertube/storage/tmp_persistent/";
           bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
           avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
           videos = lib.mkDefault "/var/lib/peertube/storage/videos/";
@@ -521,6 +522,21 @@ in {
           '';
         };
 
+        locations."~ ^/api/v1/runners/jobs/[^/]+/(update|success)$" = {
+          tryFiles = "/dev/null @api";
+          root = cfg.settings.storage.tmp;
+          priority = 1135;
+
+          extraConfig = ''
+            client_max_body_size                        12G;
+            add_header X-File-Maximum-Size              8G always;
+          '' + lib.optionalString cfg.enableWebHttps ''
+            add_header Strict-Transport-Security        'max-age=63072000; includeSubDomains';
+          '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
+            add_header Alt-Svc                          'h3=":443"; ma=86400';
+          '';
+        };
+
         locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
           tryFiles = "/dev/null @api";
           priority = 1140;
@@ -607,72 +623,33 @@ in {
           '';
         };
 
-        locations."^~ /lazy-static/avatars/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.avatars;
-          priority = 1330;
-          extraConfig = ''
-            if ($request_method = 'OPTIONS') {
-              ${nginxCommonHeaders}
-              add_header Access-Control-Max-Age         1728000;
-              add_header Cache-Control                  'no-cache';
-              add_header Content-Type                   'text/plain charset=UTF-8';
-              add_header Content-Length                 0;
-              return                                    204;
-            }
-
-            ${nginxCommonHeaders}
-            add_header Cache-Control                    'public, max-age=7200';
-
-            rewrite ^/lazy-static/avatars/(.*)$         /$1 break;
-          '';
-        };
-
-        locations."^~ /lazy-static/banners/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.avatars;
-          priority = 1340;
+        locations."^~ /download/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1410;
           extraConfig = ''
-            if ($request_method = 'OPTIONS') {
-              ${nginxCommonHeaders}
-              add_header Access-Control-Max-Age         1728000;
-              add_header Cache-Control                  'no-cache';
-              add_header Content-Type                   'text/plain charset=UTF-8';
-              add_header Content-Length                 0;
-              return                                    204;
-            }
-
-            ${nginxCommonHeaders}
-            add_header Cache-Control                    'public, max-age=7200';
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
 
-            rewrite ^/lazy-static/banners/(.*)$         /$1 break;
+            proxy_limit_rate                            5M;
           '';
         };
 
-        locations."^~ /lazy-static/previews/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.previews;
-          priority = 1350;
+        locations."^~ /static/streaming-playlists/private/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1420;
           extraConfig = ''
-            if ($request_method = 'OPTIONS') {
-              ${nginxCommonHeaders}
-              add_header Access-Control-Max-Age         1728000;
-              add_header Cache-Control                  'no-cache';
-              add_header Content-Type                   'text/plain charset=UTF-8';
-              add_header Content-Length                 0;
-              return                                    204;
-            }
-
-            ${nginxCommonHeaders}
-            add_header Cache-Control                    'public, max-age=7200';
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
 
-            rewrite ^/lazy-static/previews/(.*)$        /$1 break;
+            proxy_limit_rate                            5M;
           '';
         };
 
-        locations."^~ /static/streaming-playlists/private/" = {
+        locations."^~ /static/web-videos/private/" = {
           proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
-          priority = 1410;
+          priority = 1430;
           extraConfig = ''
             proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
             proxy_set_header Host                       $host;
@@ -684,7 +661,7 @@ in {
 
         locations."^~ /static/webseed/private/" = {
           proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
-          priority = 1420;
+          priority = 1440;
           extraConfig = ''
             proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
             proxy_set_header Host                       $host;
@@ -694,31 +671,45 @@ in {
           '';
         };
 
-        locations."^~ /static/thumbnails/" = {
+        locations."^~ /static/redundancy/" = {
           tryFiles = "$uri @api";
-          root = cfg.settings.storage.thumbnails;
-          priority = 1430;
+          root = cfg.settings.storage.redundancy;
+          priority = 1450;
           extraConfig = ''
+            set $peertube_limit_rate                    800k;
+
+            if ($request_uri ~ -fragmented.mp4$) {
+              set $peertube_limit_rate                  5M;
+            }
+
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
               add_header Access-Control-Max-Age         1728000;
-              add_header Cache-Control                  'no-cache';
               add_header Content-Type                   'text/plain charset=UTF-8';
               add_header Content-Length                 0;
               return                                    204;
             }
+            if ($request_method = 'GET') {
+              ${nginxCommonHeaders}
+
+              access_log                                off;
+            }
 
-            ${nginxCommonHeaders}
-            add_header Cache-Control                    'public, max-age=7200';
+            aio                                         threads;
+            sendfile                                    on;
+            sendfile_max_chunk                          1M;
+
+            limit_rate                                  $peertube_limit_rate;
+            limit_rate_after                            5M;
 
-            rewrite ^/static/thumbnails/(.*)$           /$1 break;
+            rewrite ^/static/redundancy/(.*)$           /$1 break;
           '';
         };
 
-        locations."^~ /static/redundancy/" = {
+        locations."^~ /static/streaming-playlists/" = {
           tryFiles = "$uri @api";
-          root = cfg.settings.storage.redundancy;
-          priority = 1440;
+          root = cfg.settings.storage.streaming_playlists;
+          priority = 1460;
           extraConfig = ''
             set $peertube_limit_rate                    800k;
 
@@ -746,14 +737,14 @@ in {
             limit_rate                                  $peertube_limit_rate;
             limit_rate_after                            5M;
 
-            rewrite ^/static/redundancy/(.*)$           /$1 break;
+            rewrite ^/static/streaming-playlists/(.*)$  /$1 break;
           '';
         };
 
-        locations."^~ /static/streaming-playlists/" = {
+        locations."^~ /static/web-videos/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.streaming_playlists;
-          priority = 1450;
+          priority = 1470;
           extraConfig = ''
             set $peertube_limit_rate                    800k;
 
@@ -788,7 +779,7 @@ in {
         locations."^~ /static/webseed/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.videos;
-          priority = 1460;
+          priority = 1480;
           extraConfig = ''
             set $peertube_limit_rate                    800k;
 
diff --git a/nixos/modules/services/web-apps/phylactery.nix b/nixos/modules/services/web-apps/phylactery.nix
index 4801bd203b4..723b38ee75d 100644
--- a/nixos/modules/services/web-apps/phylactery.nix
+++ b/nixos/modules/services/web-apps/phylactery.nix
@@ -4,7 +4,7 @@ with lib;
 let cfg = config.services.phylactery;
 in {
   options.services.phylactery = {
-    enable = mkEnableOption (lib.mdDoc "Whether to enable Phylactery server");
+    enable = mkEnableOption (lib.mdDoc "Phylactery server");
 
     host = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/web-apps/pixelfed.nix b/nixos/modules/services/web-apps/pixelfed.nix
index 159fb52476a..b0a25dcce9e 100644
--- a/nixos/modules/services/web-apps/pixelfed.nix
+++ b/nixos/modules/services/web-apps/pixelfed.nix
@@ -271,7 +271,6 @@ in {
         ensureDatabases = [ cfg.database.name ];
         ensureUsers = [{
           name = user;
-          ensurePermissions = { };
         }];
       };
 
diff --git a/nixos/modules/services/web-apps/plantuml-server.nix b/nixos/modules/services/web-apps/plantuml-server.nix
index 5ebee48c3e0..1fa69814c6c 100644
--- a/nixos/modules/services/web-apps/plantuml-server.nix
+++ b/nixos/modules/services/web-apps/plantuml-server.nix
@@ -1,123 +1,110 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    literalExpression
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkOption
+    mkPackageOptionMD
+    mkRemovedOptionModule
+    types
+    ;
 
   cfg = config.services.plantuml-server;
 
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "plantuml-server" "allowPlantumlInclude" ] "This option has been removed from PlantUML.")
+  ];
+
   options = {
     services.plantuml-server = {
-      enable = mkEnableOption (lib.mdDoc "PlantUML server");
+      enable = mkEnableOption (mdDoc "PlantUML server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.plantuml-server;
-        defaultText = literalExpression "pkgs.plantuml-server";
-        description = lib.mdDoc "PlantUML server package to use";
-      };
+      package = mkPackageOptionMD pkgs "plantuml-server" { };
 
       packages = {
-        jdk = mkOption {
-          type = types.package;
-          default = pkgs.jdk;
-          defaultText = literalExpression "pkgs.jdk";
-          description = lib.mdDoc "JDK package to use for the server";
-        };
-        jetty = mkOption {
-          type = types.package;
-          default = pkgs.jetty;
-          defaultText = literalExpression "pkgs.jetty";
-          description = lib.mdDoc "Jetty package to use for the server";
+        jdk = mkPackageOptionMD pkgs "jdk" { };
+        jetty = mkPackageOptionMD pkgs "jetty" {
+          default = "jetty_11";
+          extraDescription = ''
+            At the time of writing (v1.2023.12), PlantUML Server does not support
+            Jetty versions higher than 12.x.
+
+            Jetty 12.x has introduced major breaking changes, see
+            <https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.0> and
+            <https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-migration-11-to-12>
+          '';
         };
       };
 
       user = mkOption {
         type = types.str;
         default = "plantuml";
-        description = lib.mdDoc "User which runs PlantUML server.";
+        description = mdDoc "User which runs PlantUML server.";
       };
 
       group = mkOption {
         type = types.str;
         default = "plantuml";
-        description = lib.mdDoc "Group which runs PlantUML server.";
+        description = mdDoc "Group which runs PlantUML server.";
       };
 
       home = mkOption {
-        type = types.str;
+        type = types.path;
         default = "/var/lib/plantuml";
-        description = lib.mdDoc "Home directory of the PlantUML server instance.";
+        description = mdDoc "Home directory of the PlantUML server instance.";
       };
 
       listenHost = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = lib.mdDoc "Host to listen on.";
+        description = mdDoc "Host to listen on.";
       };
 
       listenPort = mkOption {
         type = types.int;
         default = 8080;
-        description = lib.mdDoc "Port to listen on.";
+        description = mdDoc "Port to listen on.";
       };
 
       plantumlLimitSize = mkOption {
         type = types.int;
         default = 4096;
-        description = lib.mdDoc "Limits image width and height.";
+        description = mdDoc "Limits image width and height.";
       };
 
-      graphvizPackage = mkOption {
-        type = types.package;
-        default = pkgs.graphviz;
-        defaultText = literalExpression "pkgs.graphviz";
-        description = lib.mdDoc "Package containing the dot executable.";
-      };
+      graphvizPackage = mkPackageOptionMD pkgs "graphviz" { };
 
       plantumlStats = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
+        description = mdDoc "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
       };
 
       httpAuthorization = mkOption {
         type = types.nullOr types.str;
         default = null;
-        description = lib.mdDoc "When calling the proxy endpoint, the value of HTTP_AUTHORIZATION will be used to set the HTTP Authorization header.";
-      };
-
-      allowPlantumlInclude = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Enables !include processing which can read files from the server into diagrams. Files are read relative to the current working directory.";
+        description = mdDoc "When calling the proxy endpoint, the value of HTTP_AUTHORIZATION will be used to set the HTTP Authorization header.";
       };
     };
   };
 
   config = mkIf cfg.enable {
-    users.users.${cfg.user} = {
-      isSystemUser = true;
-      group = cfg.group;
-      home = cfg.home;
-      createHome = true;
-    };
-
-    users.groups.${cfg.group} = {};
-
     systemd.services.plantuml-server = {
       description = "PlantUML server";
       wantedBy = [ "multi-user.target" ];
       path = [ cfg.home ];
+
       environment = {
         PLANTUML_LIMIT_SIZE = builtins.toString cfg.plantumlLimitSize;
         GRAPHVIZ_DOT = "${cfg.graphvizPackage}/bin/dot";
         PLANTUML_STATS = if cfg.plantumlStats then "on" else "off";
         HTTP_AUTHORIZATION = cfg.httpAuthorization;
-        ALLOW_PLANTUML_INCLUDE = if cfg.allowPlantumlInclude then "true" else "false";
       };
       script = ''
       ${cfg.packages.jdk}/bin/java \
@@ -128,13 +115,40 @@ in
           jetty.http.host=${cfg.listenHost} \
           jetty.http.port=${builtins.toString cfg.listenPort}
       '';
+
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
+        StateDirectory = mkIf (cfg.home == "/var/lib/plantuml") "plantuml";
+        StateDirectoryMode = mkIf (cfg.home == "/var/lib/plantuml") "0750";
+
+        # Hardening
+        AmbientCapabilities = [ "" ];
+        CapabilityBoundingSet = [ "" ];
+        DynamicUser = true;
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateNetwork = false;
         PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = "strict";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" ];
       };
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ truh ];
+  meta.maintainers = with lib.maintainers; [ truh anthonyroussel ];
 }
diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix
index e2d5cdc4f7c..300a0f892ef 100644
--- a/nixos/modules/services/web-apps/plausible.nix
+++ b/nixos/modules/services/web-apps/plausible.nix
@@ -11,13 +11,6 @@ in {
 
     package = mkPackageOptionMD pkgs "plausible" { };
 
-    releaseCookiePath = mkOption {
-      type = with types; either str path;
-      description = lib.mdDoc ''
-        The path to the file with release cookie. (used for remote connection to the running node).
-      '';
-    };
-
     adminUser = {
       name = mkOption {
         default = "admin";
@@ -78,9 +71,9 @@ in {
     server = {
       disableRegistration = mkOption {
         default = true;
-        type = types.bool;
+        type = types.enum [true false "invite_only"];
         description = lib.mdDoc ''
-          Whether to prohibit creating an account in plausible's UI.
+          Whether to prohibit creating an account in plausible's UI or allow on `invite_only`.
         '';
       };
       secretKeybaseFile = mkOption {
@@ -92,6 +85,13 @@ in {
           framework docs](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content).
         '';
       };
+      listenAddress = mkOption {
+        default = "127.0.0.1";
+        type = types.str;
+        description = lib.mdDoc ''
+          The IP address on which the server is listening.
+        '';
+      };
       port = mkOption {
         default = 8000;
         type = types.port;
@@ -162,6 +162,10 @@ in {
     };
   };
 
+  imports = [
+    (mkRemovedOptionModule [ "services" "plausible" "releaseCookiePath" ] "Plausible uses no distributed Erlang features, so this option is no longer necessary and was removed")
+  ];
+
   config = mkIf cfg.enable {
     assertions = [
       { assertion = cfg.adminUser.activate -> cfg.database.postgres.setup;
@@ -180,8 +184,6 @@ in {
       enable = true;
     };
 
-    services.epmd.enable = true;
-
     environment.systemPackages = [ cfg.package ];
 
     systemd.services = mkMerge [
@@ -209,7 +211,33 @@ in {
             # Configuration options from
             # https://plausible.io/docs/self-hosting-configuration
             PORT = toString cfg.server.port;
-            DISABLE_REGISTRATION = boolToString cfg.server.disableRegistration;
+            LISTEN_IP = cfg.server.listenAddress;
+
+            # Note [plausible-needs-no-erlang-distributed-features]:
+            # Plausible does not use, and does not plan to use, any of
+            # Erlang's distributed features, see:
+            #     https://github.com/plausible/analytics/pull/1190#issuecomment-1018820934
+            # Thus, disable distribution for improved simplicity and security:
+            #
+            # When distribution is enabled,
+            # Elixir spwans the Erlang VM, which will listen by default on all
+            # interfaces for messages between Erlang nodes (capable of
+            # remote code execution); it can be protected by a cookie; see
+            # https://erlang.org/doc/reference_manual/distributed.html#security).
+            #
+            # It would be possible to restrict the interface to one of our choice
+            # (e.g. localhost or a VPN IP) similar to how we do it with `listenAddress`
+            # for the Plausible web server; if distribution is ever needed in the future,
+            # https://github.com/NixOS/nixpkgs/pull/130297 shows how to do it.
+            #
+            # But since Plausible does not use this feature in any way,
+            # we just disable it.
+            RELEASE_DISTRIBUTION = "none";
+            # Additional safeguard, in case `RELEASE_DISTRIBUTION=none` ever
+            # stops disabling the start of EPMD.
+            ERL_EPMD_ADDRESS = "127.0.0.1";
+
+            DISABLE_REGISTRATION = if isBool cfg.server.disableRegistration then boolToString cfg.server.disableRegistration else cfg.server.disableRegistration;
 
             RELEASE_TMP = "/var/lib/plausible/tmp";
             # Home is needed to connect to the node with iex
@@ -238,7 +266,10 @@ in {
           path = [ cfg.package ]
             ++ optional cfg.database.postgres.setup config.services.postgresql.package;
           script = ''
-            export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )"
+            # Elixir does not start up if `RELEASE_COOKIE` is not set,
+            # even though we set `RELEASE_DISTRIBUTION=none` so the cookie should be unused.
+            # Thus, make a random one, which should then be ignored.
+            export RELEASE_COOKIE=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 20)
             export ADMIN_USER_PWD="$(< $CREDENTIALS_DIRECTORY/ADMIN_USER_PWD )"
             export SECRET_KEY_BASE="$(< $CREDENTIALS_DIRECTORY/SECRET_KEY_BASE )"
 
@@ -265,7 +296,6 @@ in {
             LoadCredential = [
               "ADMIN_USER_PWD:${cfg.adminUser.passwordFile}"
               "SECRET_KEY_BASE:${cfg.server.secretKeybaseFile}"
-              "RELEASE_COOKIE:${cfg.releaseCookiePath}"
             ] ++ lib.optionals (cfg.mail.smtp.passwordFile != null) [ "SMTP_USER_PWD:${cfg.mail.smtp.passwordFile}"];
           };
         };
@@ -296,6 +326,6 @@ in {
     ];
   };
 
-  meta.maintainers = with maintainers; [ ma27 ];
+  meta.maintainers = with maintainers; [ ];
   meta.doc = ./plausible.md;
 }
diff --git a/nixos/modules/services/web-apps/rimgo.nix b/nixos/modules/services/web-apps/rimgo.nix
new file mode 100644
index 00000000000..4d35473fda3
--- /dev/null
+++ b/nixos/modules/services/web-apps/rimgo.nix
@@ -0,0 +1,107 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.rimgo;
+  inherit (lib)
+    mkOption
+    mkEnableOption
+    mkPackageOption
+    mkDefault
+    mkIf
+    types
+    literalExpression
+    optionalString
+    getExe
+    mapAttrs
+  ;
+in
+{
+  options.services.rimgo = {
+    enable = mkEnableOption "rimgo";
+    package = mkPackageOption pkgs "rimgo" { };
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = with types; attrsOf str;
+        options = {
+          PORT = mkOption {
+            type = types.port;
+            default = 3000;
+            example = 69420;
+            description = "The port to use.";
+          };
+          ADDRESS = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            example = "1.1.1.1";
+            description = "The address to listen on.";
+          };
+        };
+      };
+      example = literalExpression ''
+        {
+          PORT = 69420;
+          FORCE_WEBP = "1";
+        }
+      '';
+      description = ''
+        Settings for rimgo, see [the official documentation](https://rimgo.codeberg.page/docs/usage/configuration/) for supported options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.rimgo = {
+      description = "Rimgo";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      environment = mapAttrs (_: toString) cfg.settings;
+      serviceConfig = {
+        ExecStart = getExe cfg.package;
+        AmbientCapabilities = mkIf (cfg.settings.PORT < 1024) [
+          "CAP_NET_BIND_SERVICE"
+        ];
+        DynamicUser = true;
+        Restart = "on-failure";
+        RestartSec = "5s";
+        CapabilityBoundingSet = [
+          (optionalString (cfg.settings.PORT < 1024) "CAP_NET_BIND_SERVICE")
+        ];
+        DeviceAllow = [ "" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = cfg.settings.PORT >= 1024;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ quantenzitrone ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix
index f0505e052e1..71b5ad4d4c0 100644
--- a/nixos/modules/services/web-apps/shiori.nix
+++ b/nixos/modules/services/web-apps/shiori.nix
@@ -29,6 +29,13 @@ in {
         default = 8080;
         description = lib.mdDoc "The port of the Shiori web application";
       };
+
+      webRoot = mkOption {
+        type = types.str;
+        default = "/";
+        example = "/shiori";
+        description = lib.mdDoc "The root of the Shiori web application";
+      };
     };
   };
 
@@ -40,7 +47,7 @@ in {
       environment.SHIORI_DIR = "/var/lib/shiori";
 
       serviceConfig = {
-        ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}'";
+        ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}' --webroot '${webRoot}'";
 
         DynamicUser = true;
         StateDirectory = "shiori";
diff --git a/nixos/modules/services/web-apps/snipe-it.nix b/nixos/modules/services/web-apps/snipe-it.nix
index e861a418519..4fbf2bad750 100644
--- a/nixos/modules/services/web-apps/snipe-it.nix
+++ b/nixos/modules/services/web-apps/snipe-it.nix
@@ -18,19 +18,23 @@ let
   inherit (snipe-it.passthru) phpPackage;
 
   # shell script for local administration
-  artisan = pkgs.writeScriptBin "snipe-it" ''
+  artisan = (pkgs.writeScriptBin "snipe-it" ''
     #! ${pkgs.runtimeShell}
-    cd ${snipe-it}
+    cd "${snipe-it}/share/php/snipe-it"
     sudo=exec
     if [[ "$USER" != ${user} ]]; then
       sudo='exec /run/wrappers/bin/sudo -u ${user}'
     fi
     $sudo ${phpPackage}/bin/php artisan $*
-  '';
+  '').overrideAttrs (old: {
+    meta = old.meta // {
+      mainProgram = "snipe-it";
+    };
+  });
 in {
   options.services.snipe-it = {
 
-    enable = mkEnableOption (lib.mdDoc "A free open source IT asset/license management system");
+    enable = mkEnableOption (lib.mdDoc "snipe-it, a free open source IT asset/license management system");
 
     user = mkOption {
       default = "snipeit";
@@ -357,7 +361,7 @@ in {
     services.nginx = {
       enable = mkDefault true;
       virtualHosts."${cfg.hostName}" = mkMerge [ cfg.nginx {
-        root = mkForce "${snipe-it}/public";
+        root = mkForce "${snipe-it}/share/php/snipe-it/public";
         extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;";
         locations = {
           "/" = {
@@ -394,7 +398,7 @@ in {
         RuntimeDirectory = "snipe-it/cache";
         RuntimeDirectoryMode = "0700";
       };
-      path = [ pkgs.replace-secret ];
+      path = [ pkgs.replace-secret artisan ];
       script =
         let
           isSecret  = v: isAttrs v && v ? _secret && (isString v._secret || builtins.isPath v._secret);
@@ -451,7 +455,7 @@ in {
           rm "${cfg.dataDir}"/bootstrap/cache/*.php || true
 
           # migrate db
-          ${phpPackage}/bin/php artisan migrate --force
+          ${lib.getExe artisan} migrate --force
 
           # A placeholder file for invalid barcodes
           invalid_barcode_location="${cfg.dataDir}/public/uploads/barcodes/invalid_barcode.gif"
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 592ab253f7d..a8fb37d2c5e 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -430,7 +430,7 @@ let
           background processes while not running tt-rss, this method is generally
           viable to keep your feeds up to date.
           Still, there are more robust (and recommended) updating methods
-          available, you can read about them here: http://tt-rss.org/wiki/UpdatingFeeds
+          available, you can read about them here: <https://tt-rss.org/wiki/UpdatingFeeds>
         '';
       };
 
@@ -529,6 +529,15 @@ let
         assertion = cfg.database.password != null -> cfg.database.passwordFile == null;
         message = "Cannot set both password and passwordFile";
       }
+      {
+        assertion = cfg.database.createLocally -> cfg.database.name == cfg.user && cfg.database.user == cfg.user;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.tt-rss.database.createLocally` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
     ];
 
     services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
@@ -632,8 +641,8 @@ let
       enable = mkDefault true;
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
-        { name = cfg.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        { name = cfg.database.user;
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index d4c987da114..5d2e775d452 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -34,7 +34,7 @@ let
       # copy additional plugin(s), theme(s) and language(s)
       ${concatStringsSep "\n" (mapAttrsToList (name: theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${name}") cfg.themes)}
       ${concatStringsSep "\n" (mapAttrsToList (name: plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${name}") cfg.plugins)}
-      ${concatMapStringsSep "\n" (language: "cp -r ${language} $out/share/wordpress/wp-content/languages/") cfg.languages}
+      ${concatMapStringsSep "\n" (language: "cp -r ${language}/* $out/share/wordpress/wp-content/languages/") cfg.languages}
     '';
   };
 
diff --git a/nixos/modules/services/web-apps/writefreely.nix b/nixos/modules/services/web-apps/writefreely.nix
index a7671aa717f..f92afa9276e 100644
--- a/nixos/modules/services/web-apps/writefreely.nix
+++ b/nixos/modules/services/web-apps/writefreely.nix
@@ -120,7 +120,7 @@ let
     withConfigFile ''
       query () {
         local result=$(${sqlite}/bin/sqlite3 \
-          '${cfg.stateDir}/${settings.database.filename}'
+          '${cfg.stateDir}/${settings.database.filename}' \
           "$1" \
         )
 
diff --git a/nixos/modules/services/web-apps/zitadel.nix b/nixos/modules/services/web-apps/zitadel.nix
new file mode 100644
index 00000000000..99b0a0bc56f
--- /dev/null
+++ b/nixos/modules/services/web-apps/zitadel.nix
@@ -0,0 +1,223 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.zitadel;
+
+  settingsFormat = pkgs.formats.yaml { };
+in
+{
+  options.services.zitadel =
+    let inherit (lib) mkEnableOption mkOption mkPackageOption types;
+    in {
+      enable = mkEnableOption "ZITADEL, a user and identity access management platform";
+
+      package = mkPackageOption pkgs "ZITADEL" { default = [ "zitadel" ]; };
+
+      user = mkOption {
+        type = types.str;
+        default = "zitadel";
+        description = "The user to run ZITADEL under.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "zitadel";
+        description = "The group to run ZITADEL under.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open the port specified in `listenPort` in the firewall.
+        '';
+      };
+
+      masterKeyFile = mkOption {
+        type = types.path;
+        description = ''
+          Path to a file containing a master encryption key for ZITADEL. The
+          key must be 32 bytes.
+        '';
+      };
+
+      tlsMode = mkOption {
+        type = types.enum [ "external" "enabled" "disabled" ];
+        default = "external";
+        example = "enabled";
+        description = ''
+          The TLS mode to use. Options are:
+
+          - enabled: ZITADEL accepts HTTPS connections directly. You must
+            configure TLS if this option is selected.
+          - external: ZITADEL forces HTTPS connections, with TLS terminated at a
+            reverse proxy.
+          - disabled: ZITADEL accepts HTTP connections only. Should only be used
+            for testing.
+        '';
+      };
+
+      settings = mkOption {
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            Port = mkOption {
+              type = types.port;
+              default = 8080;
+              description = "The port that ZITADEL listens on.";
+            };
+
+            TLS = {
+              KeyPath = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                description = "Path to the TLS certificate private key.";
+              };
+              Key = mkOption {
+                type = types.nullOr types.str;
+                default = null;
+                description = ''
+                  The TLS certificate private key, as a base64-encoded string.
+
+                  Note that the contents of this option will be added to the Nix
+                  store as world-readable plain text. Set
+                  [KeyPath](#opt-services.zitadel.settings.TLS.KeyPath) instead
+                  if this is undesired.
+                '';
+              };
+              CertPath = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                description = "Path to the TLS certificate.";
+              };
+              Cert = mkOption {
+                type = types.nullOr types.str;
+                default = null;
+                description = ''
+                  The TLS certificate, as a base64-encoded string.
+
+                  Note that the contents of this option will be added to the Nix
+                  store as world-readable plain text. Set
+                  [CertPath](#opt-services.zitadel.settings.TLS.CertPath) instead
+                  if this is undesired.
+                '';
+              };
+            };
+          };
+        };
+        default = { };
+        example = lib.literalExpression ''
+          {
+            Port = 8123;
+            ExternalDomain = "example.com";
+            TLS = {
+              CertPath = "/path/to/cert.pem";
+              KeyPath = "/path/to/cert.key";
+            };
+            Database.cockroach.Host = "db.example.com";
+          };
+        '';
+        description = ''
+          Contents of the runtime configuration file. See
+          https://zitadel.com/docs/self-hosting/manage/configure for more
+          details.
+        '';
+      };
+
+      extraSettingsPaths = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        description = ''
+          A list of paths to extra settings files. These will override the
+          values set in [settings](#opt-services.zitadel.settings). Useful if
+          you want to keep sensitive secrets out of the Nix store.
+        '';
+      };
+
+      steps = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        example = lib.literalExpression ''
+          {
+            FirstInstance = {
+              InstanceName = "Example";
+              Org.Human = {
+                UserName = "foobar";
+                FirstName = "Foo";
+                LastName = "Bar";
+              };
+            };
+          }
+        '';
+        description = ''
+          Contents of the database initialization config file. See
+          https://zitadel.com/docs/self-hosting/manage/configure for more
+          details.
+        '';
+      };
+
+      extraStepsPaths = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        description = ''
+          A list of paths to extra steps files. These will override the values
+          set in [steps](#opt-services.zitadel.steps). Useful if you want to
+          keep sensitive secrets out of the Nix store.
+        '';
+      };
+    };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [{
+      assertion = cfg.tlsMode == "enabled"
+        -> ((cfg.settings.TLS.Key != null || cfg.settings.TLS.KeyPath != null)
+        && (cfg.settings.TLS.Cert != null || cfg.settings.TLS.CertPath
+        != null));
+      message = ''
+        A TLS certificate and key must be configured in
+        services.zitadel.settings.TLS if services.zitadel.tlsMode is enabled.
+      '';
+    }];
+
+    networking.firewall.allowedTCPPorts =
+      lib.mkIf cfg.openFirewall [ cfg.settings.Port ];
+
+    systemd.services.zitadel =
+      let
+        configFile = settingsFormat.generate "config.yaml" cfg.settings;
+        stepsFile = settingsFormat.generate "steps.yaml" cfg.steps;
+
+        args = lib.cli.toGNUCommandLineShell { } {
+          config = cfg.extraSettingsPaths ++ [ configFile ];
+          steps = cfg.extraStepsPaths ++ [ stepsFile ];
+          masterkeyFile = cfg.masterKeyFile;
+          inherit (cfg) tlsMode;
+        };
+      in
+      {
+        description = "ZITADEL identity access management";
+        path = [ cfg.package ];
+        wantedBy = [ "multi-user.target" ];
+
+        script = ''
+          zitadel start-from-init ${args}
+        '';
+
+        serviceConfig = {
+          Type = "simple";
+          User = cfg.user;
+          Group = cfg.group;
+          Restart = "on-failure";
+        };
+      };
+
+    users.users.zitadel = lib.mkIf (cfg.user == "zitadel") {
+      isSystemUser = true;
+      group = cfg.group;
+    };
+    users.groups.zitadel = lib.mkIf (cfg.group == "zitadel") { };
+  };
+
+  meta.maintainers = with lib.maintainers; [ Sorixelle ];
+}
diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix
index 80fb24fe2c5..47b4c6ab416 100644
--- a/nixos/modules/services/web-servers/garage.nix
+++ b/nixos/modules/services/web-servers/garage.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.services.garage;
-  toml = pkgs.formats.toml {};
+  toml = pkgs.formats.toml { };
   configFile = toml.generate "garage.toml" cfg.settings;
 in
 {
@@ -19,8 +19,8 @@ in
     extraEnvironment = mkOption {
       type = types.attrsOf types.str;
       description = lib.mdDoc "Extra environment variables to pass to the Garage server.";
-      default = {};
-      example = { RUST_BACKTRACE="yes"; };
+      default = { };
+      example = { RUST_BACKTRACE = "yes"; };
     };
 
     environmentFile = mkOption {
@@ -30,7 +30,7 @@ in
     };
 
     logLevel = mkOption {
-      type = types.enum (["info" "debug" "trace"]);
+      type = types.enum ([ "info" "debug" "trace" ]);
       default = "info";
       example = "debug";
       description = lib.mdDoc "Garage log level, see <https://garagehq.deuxfleurs.fr/documentation/quick-start/#launching-the-garage-server> for examples.";
@@ -65,12 +65,8 @@ in
     };
 
     package = mkOption {
-      # TODO: when 23.05 is released and if Garage 0.9 is the default, put a stateVersion check.
-      default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.garage_0_8
-                else pkgs.garage_0_7;
-      defaultText = literalExpression "pkgs.garage_0_7";
       type = types.package;
-      description = lib.mdDoc "Garage package to use, if you are upgrading from a major version, please read NixOS and Garage release notes for upgrade instructions.";
+      description = lib.mdDoc "Garage package to use, needs to be set explicitly. If you are upgrading from a major version, please read NixOS and Garage release notes for upgrade instructions.";
     };
   };
 
@@ -90,7 +86,7 @@ in
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/garage server";
 
-        StateDirectory = mkIf (hasPrefix "/var/lib/garage" cfg.settings.data_dir && hasPrefix "/var/lib/garage" cfg.settings.metadata_dir) "garage";
+        StateDirectory = mkIf (hasPrefix "/var/lib/garage" cfg.settings.data_dir || hasPrefix "/var/lib/garage" cfg.settings.metadata_dir) "garage";
         DynamicUser = lib.mkDefault true;
         ProtectHome = true;
         NoNewPrivileges = true;
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
index 4434965b217..9d30fdc0caa 100644
--- a/nixos/modules/services/web-servers/hydron.nix
+++ b/nixos/modules/services/web-servers/hydron.nix
@@ -93,7 +93,7 @@ in with lib; {
       ensureDatabases = [ "hydron" ];
       ensureUsers = [
         { name = "hydron";
-          ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-servers/jboss/builder.sh b/nixos/modules/services/web-servers/jboss/builder.sh
index ac573089cd5..8c49b87db06 100644
--- a/nixos/modules/services/web-servers/jboss/builder.sh
+++ b/nixos/modules/services/web-servers/jboss/builder.sh
@@ -1,6 +1,6 @@
 set -e
 
-if [ -e .attrs.sh ]; then source .attrs.sh; fi
+if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; elif [ -f .attrs.sh ]; then . .attrs.sh; fi
 source $stdenv/setup
 
 mkdir -p $out/bin
diff --git a/nixos/modules/services/web-servers/keter/default.nix b/nixos/modules/services/web-servers/keter/default.nix
index 3916c486475..0cd9c30cea1 100644
--- a/nixos/modules/services/web-servers/keter/default.nix
+++ b/nixos/modules/services/web-servers/keter/default.nix
@@ -16,7 +16,7 @@ in
   options.services.keter = {
     enable = lib.mkEnableOption (lib.mdDoc ''keter, a web app deployment manager.
 Note that this module only support loading of webapps:
-Keep an old app running and swap the ports when the new one is booted.
+Keep an old app running and swap the ports when the new one is booted
 '');
 
     root = lib.mkOption {
diff --git a/nixos/modules/services/web-servers/lighttpd/default.nix b/nixos/modules/services/web-servers/lighttpd/default.nix
index 0438e12e7da..eaa113c0d52 100644
--- a/nixos/modules/services/web-servers/lighttpd/default.nix
+++ b/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -10,7 +10,7 @@ let
 
   # List of known lighttpd modules, ordered by how the lighttpd documentation
   # recommends them being imported:
-  # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+  # https://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
   #
   # Some modules are always imported and should not appear in the config:
   # disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ];
@@ -84,8 +84,8 @@ let
       # server.modules += () entries in each sub-service extraConfig snippet,
       # read this:
       #
-      #   http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
-      #   http://redmine.lighttpd.net/issues/2337
+      #   https://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+      #   https://redmine.lighttpd.net/issues/2337
       #
       # Basically, lighttpd doesn't want to load (or even silently ignore) a
       # module for a second time, and there is no way to check if a module has
@@ -253,6 +253,7 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig.ExecStart = "${cfg.package}/sbin/lighttpd -D -f ${configFile}";
+      serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR1 $MAINPID";
       # SIGINT => graceful shutdown
       serviceConfig.KillSignal = "SIGINT";
     };
diff --git a/nixos/modules/services/web-servers/mighttpd2.nix b/nixos/modules/services/web-servers/mighttpd2.nix
index 2d887af87c7..bdd6d8b62aa 100644
--- a/nixos/modules/services/web-servers/mighttpd2.nix
+++ b/nixos/modules/services/web-servers/mighttpd2.nix
@@ -44,7 +44,7 @@ in {
       type = types.lines;
       description = lib.mdDoc ''
         Verbatim config file to use
-        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+        (see https://kazu-yamamoto.github.io/mighttpd2/config.html)
       '';
     };
 
@@ -78,7 +78,7 @@ in {
       type = types.lines;
       description = lib.mdDoc ''
         Verbatim routing file to use
-        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+        (see https://kazu-yamamoto.github.io/mighttpd2/config.html)
       '';
     };
 
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 7a7fb4061ee..cf70dc32594 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -35,6 +35,7 @@ let
   compressMimeTypes = [
     "application/atom+xml"
     "application/geo+json"
+    "application/javascript" # Deprecated by IETF RFC 9239, but still widely used
     "application/json"
     "application/ld+json"
     "application/manifest+json"
@@ -146,6 +147,10 @@ let
     error_log ${cfg.logError};
     daemon off;
 
+    ${optionalString cfg.enableQuicBPF ''
+      quic_bpf on;
+    ''}
+
     ${cfg.config}
 
     ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
@@ -325,7 +330,7 @@ let
         listenString = { addr, port, ssl, proxyProtocol ? false, extraParameters ? [], ... }:
           # UDP listener for QUIC transport protocol.
           (optionalString (ssl && vhost.quic) ("
-            listen ${addr}:${toString port} quic "
+            listen ${addr}${optionalString (port != null) ":${toString port}"} quic "
           + optionalString vhost.default "default_server "
           + optionalString vhost.reuseport "reuseport "
           + optionalString (extraParameters != []) (concatStringsSep " "
@@ -334,7 +339,7 @@ let
             in filter isCompatibleParameter extraParameters))
           + ";"))
           + "
-            listen ${addr}:${toString port} "
+            listen ${addr}${optionalString (port != null) ":${toString port}"} "
           + optionalString (ssl && vhost.http2 && oldHTTP2) "http2 "
           + optionalString ssl "ssl "
           + optionalString vhost.default "default_server "
@@ -783,6 +788,19 @@ in
         '';
       };
 
+      enableQuicBPF = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enables routing of QUIC packets using eBPF. When enabled, this allows
+          to support QUIC connection migration. The directive is only supported
+          on Linux 5.7+.
+          Note that enabling this option will make nginx run with extended
+          capabilities that are usually limited to processes running as root
+          namely `CAP_SYS_ADMIN` and `CAP_NET_ADMIN`.
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "nginx";
@@ -937,7 +955,7 @@ in
         default = {};
         description = lib.mdDoc ''
           Configure a proxy cache path entry.
-          See <http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
+          See <https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
         '';
       };
 
@@ -1126,6 +1144,14 @@ in
       }
 
       {
+        assertion = cfg.package.pname != "nginxQuic" -> !(cfg.enableQuicBPF);
+        message = ''
+          services.nginx.enableQuicBPF requires using nginxQuic package,
+          which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`.
+        '';
+      }
+
+      {
         assertion = cfg.package.pname != "nginxQuic" -> all (host: !host.quic) (attrValues virtualHosts);
         message = ''
           services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic package,
@@ -1224,8 +1250,8 @@ in
         # New file permissions
         UMask = "0027"; # 0640 / 0750
         # Capabilities
-        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
-        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ] ++ optionals cfg.enableQuicBPF [ "CAP_SYS_ADMIN" "CAP_NET_ADMIN" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ] ++ optionals cfg.enableQuicBPF [ "CAP_SYS_ADMIN" "CAP_NET_ADMIN" ];
         # Security
         NoNewPrivileges = true;
         # Sandboxing (sorted by occurrence in https://www.freedesktop.org/software/systemd/man/systemd.exec.html)
@@ -1250,6 +1276,7 @@ in
         # System Call Filtering
         SystemCallArchitectures = "native";
         SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" ]
+          ++ optional cfg.enableQuicBPF [ "bpf" ]
           ++ optionals ((cfg.package != pkgs.tengine) && (cfg.package != pkgs.openresty) && (!lib.any (mod: (mod.disableIPC or false)) cfg.package.modules)) [ "~@ipc" ];
       };
     };
@@ -1314,6 +1341,11 @@ in
       nginx.gid = config.ids.gids.nginx;
     };
 
+    # do not delete the default temp directories created upon nginx startup
+    systemd.tmpfiles.rules = [
+      "X /tmp/systemd-private-%b-nginx.service-*/tmp/nginx_*"
+    ];
+
     services.logrotate.settings.nginx = mapAttrs (_: mkDefault) {
       files = "/var/log/nginx/*.log";
       frequency = "weekly";
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index 7636c1b2611..9db4c8e2302 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -31,12 +31,15 @@ with lib;
         options = {
           addr = mkOption {
             type = str;
-            description = lib.mdDoc "IP address.";
+            description = lib.mdDoc "Listen address.";
           };
           port = mkOption {
-            type = port;
-            description = lib.mdDoc "Port number.";
-            default = 80;
+            type = types.nullOr port;
+            description = lib.mdDoc ''
+              Port number to listen on.
+              If unset and the listen address is not a socket then nginx defaults to 80.
+            '';
+            default = null;
           };
           ssl = mkOption {
             type = bool;
@@ -60,6 +63,7 @@ with lib;
       example = [
         { addr = "195.154.1.1"; port = 443; ssl = true; }
         { addr = "192.154.1.1"; port = 80; }
+        { addr = "unix:/var/run/nginx.sock"; }
       ];
       description = lib.mdDoc ''
         Listen addresses and ports for this virtual host.
diff --git a/nixos/modules/services/web-servers/rustus.nix b/nixos/modules/services/web-servers/rustus.nix
index 878d790e366..6d3b2e6a65d 100644
--- a/nixos/modules/services/web-servers/rustus.nix
+++ b/nixos/modules/services/web-servers/rustus.nix
@@ -8,7 +8,7 @@ in
 
   options.services.rustus = {
 
-    enable = mkEnableOption (lib.mdDoc "TUS protocol implementation in Rust.");
+    enable = mkEnableOption (lib.mdDoc "TUS protocol implementation in Rust");
 
     host = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/web-servers/stargazer.nix b/nixos/modules/services/web-servers/stargazer.nix
index f0c3cf8787e..18f57363137 100644
--- a/nixos/modules/services/web-servers/stargazer.nix
+++ b/nixos/modules/services/web-servers/stargazer.nix
@@ -204,11 +204,9 @@ in
     };
 
     # Create default cert store
-    system.activationScripts.makeStargazerCertDir =
-      lib.optionalAttrs (cfg.store == /var/lib/gemini/certs) ''
-        mkdir -p /var/lib/gemini/certs
-        chown -R ${cfg.user}:${cfg.group} /var/lib/gemini/certs
-      '';
+    systemd.tmpfiles.rules = lib.mkIf (cfg.store == /var/lib/gemini/certs) [
+      ''d /var/lib/gemini/certs - "${cfg.user}" "${cfg.group}" -''
+    ];
 
     users.users = lib.optionalAttrs (cfg.user == "stargazer") {
       stargazer = {
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index bb42c52b69c..f1e4d930402 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -221,7 +221,7 @@ in
 
       # Default Fonts
       fonts.packages = with pkgs; [
-        source-code-pro # Default monospace font in 3.32
+        dejavu_fonts # Default monospace font in LMDE 6+
         ubuntu_font_family # required for default theme
       ];
     })
diff --git a/nixos/modules/services/x11/desktop-managers/deepin.nix b/nixos/modules/services/x11/desktop-managers/deepin.nix
index b2369e2426f..28d75130589 100644
--- a/nixos/modules/services/x11/desktop-managers/deepin.nix
+++ b/nixos/modules/services/x11/desktop-managers/deepin.nix
@@ -15,7 +15,7 @@ in
   options = {
 
     services.xserver.desktopManager.deepin = {
-      enable = mkEnableOption (lib.mdDoc "Enable Deepin desktop manager");
+      enable = mkEnableOption (lib.mdDoc "Deepin desktop manager");
       extraGSettingsOverrides = mkOption {
         default = "";
         type = types.lines;
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 1512b5fdf8a..28dd408c923 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -63,7 +63,7 @@ in
         # make available for D-BUS user services
         #export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}:${config.system.path}/share:${e.efl}/share
 
-        # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+        # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
         ${pkgs.xdg-user-dirs}/bin/xdg-user-dirs-update
       fi
     '';
@@ -90,7 +90,7 @@ in
         };
     };
 
-    environment.etc."X11/xkb".source = xcfg.xkbDir;
+    environment.etc."X11/xkb".source = xcfg.xkb.dir;
 
     fonts.packages = [ pkgs.dejavu_fonts pkgs.ubuntu_font_family ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index fecdd86deb8..12bdd933337 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -229,7 +229,7 @@ in
         panelModulePackages = mkOption {
           default = [ pkgs.gnome.gnome-applets ];
           defaultText = literalExpression "[ pkgs.gnome.gnome-applets ]";
-          type = types.listOf types.path;
+          type = types.listOf types.package;
           description = lib.mdDoc ''
             Packages containing modules that should be made available to `gnome-panel` (usually for applets).
 
@@ -282,9 +282,6 @@ in
 
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
-
-       # If gnome is installed, build vim for gtk3 too.
-      nixpkgs.config.vim.gui = "gtk3";
     })
 
     (mkIf flashbackEnabled {
@@ -297,8 +294,7 @@ in
           map
             (wm:
               pkgs.gnome.gnome-flashback.mkSessionForWm {
-                inherit (wm) wmName wmLabel wmCommand enableGnomePanel;
-                inherit (cfg.flashback) panelModulePackages;
+                inherit (wm) wmName wmLabel wmCommand;
               }
             ) flashbackWms;
 
@@ -310,10 +306,16 @@ in
         gnome-flashback
       ] ++ map gnome-flashback.mkSystemdTargetForWm flashbackWms;
 
-      # gnome-panel needs these for menu applet
-      environment.sessionVariables.XDG_DATA_DIRS = [ "${pkgs.gnome.gnome-flashback}/share" ];
-      # TODO: switch to sessionVariables (resolve conflict)
-      environment.variables.XDG_CONFIG_DIRS = [ "${pkgs.gnome.gnome-flashback}/etc/xdg" ];
+      environment.systemPackages = with pkgs.gnome; [
+        gnome-flashback
+        (gnome-panel-with-modules.override {
+          panelModulePackages = cfg.flashback.panelModulePackages;
+        })
+      ]
+      # For /share/applications/${wmName}.desktop
+      ++ (map (wm: gnome-flashback.mkWmApplication { inherit (wm) wmName wmLabel wmCommand; }) flashbackWms)
+      # For /share/gnome-session/sessions/gnome-flashback-${wmName}.session
+      ++ (map (wm: gnome-flashback.mkGnomeSession { inherit (wm) wmName wmLabel enableGnomePanel; }) flashbackWms);
     })
 
     (mkIf serviceCfg.core-os-services.enable {
@@ -453,7 +455,7 @@ in
             pkgs.glib # for gsettings program
             pkgs.gnome-menus
             pkgs.gtk3.out # for gtk-launch program
-            pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+            pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
           ];
         in
         mandatoryPackages
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index eef7aa14057..d82d19b26cd 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -201,7 +201,7 @@ in
         onboard
         orca # elementary/greeter#668
         sound-theme-freedesktop
-        xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+        xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
       ]) ++ (with pkgs.pantheon; [
         # Artwork
         elementary-gtk-theme
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 282a34f6b01..361dbe879a1 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -29,7 +29,7 @@ let
   libsForQt5 = pkgs.plasma5Packages;
   inherit (libsForQt5) kdeGear kdeFrameworks plasma5;
   inherit (lib)
-    getBin optionalString literalExpression
+    getBin optionalAttrs optionalString literalExpression
     mkRemovedOptionModule mkRenamedOptionModule
     mkDefault mkIf mkMerge mkOption mkPackageOptionMD types;
 
@@ -178,7 +178,7 @@ in
           capabilities = "cap_sys_nice+ep";
           source = "${getBin plasma5.kwin}/bin/kwin_wayland";
         };
-      } // mkIf (!cfg.runUsingSystemd) {
+      } // optionalAttrs (!cfg.runUsingSystemd) {
         start_kdeinit = {
           setuid = true;
           owner = "root";
@@ -309,7 +309,7 @@ in
         "/share"
       ];
 
-      environment.etc."X11/xkb".source = xcfg.xkbDir;
+      environment.etc."X11/xkb".source = xcfg.xkb.dir;
 
       environment.sessionVariables = {
         PLASMA_USE_QT_SCALING = mkIf cfg.useQtScaling "1";
@@ -379,7 +379,7 @@ in
       # Update the start menu for each user that is currently logged in
       system.userActivationScripts.plasmaSetup = activationScript;
 
-      nixpkgs.config.firefox.enablePlasmaBrowserIntegration = true;
+      programs.firefox.nativeMessagingHosts.packages = [ pkgs.plasma5Packages.plasma-browser-integration ];
     })
 
     (mkIf (cfg.kwinrc != {}) {
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 1f08ded7c96..16a7ff1a4bd 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -96,7 +96,7 @@ let
       )}
 
       # Speed up application start by 50-150ms according to
-      # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
+      # https://kdemonkey.blogspot.com/2008/04/magic-trick.html
       compose_cache="''${XCOMPOSECACHE:-$HOME/.compose-cache}"
       mkdir -p "$compose_cache"
       # To avoid accidentally deleting a wrongly set up XCOMPOSECACHE directory,
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
index 8d6bfa98a7e..dede7680ecb 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
@@ -61,7 +61,7 @@ in
 
     services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
 
-    nixpkgs.config.lightdm-tiny-greeter.conf =
+    services.xserver.displayManager.lightdm.greeter =
     let
       configHeader = ''
         #include <gtk/gtk.h>
@@ -69,13 +69,11 @@ in
         static const char *pass_text = "${cfg.label.pass}";
         static const char *session = "${dmcfg.defaultSession}";
       '';
+      config = optionalString (cfg.extraConfig != "") (configHeader + cfg.extraConfig);
+      package = pkgs.lightdm-tiny-greeter.override { conf = config; };
     in
-      optionalString (cfg.extraConfig != "")
-        (configHeader + cfg.extraConfig);
-
-    services.xserver.displayManager.lightdm.greeter =
       mkDefault {
-        package = pkgs.lightdm-tiny-greeter.xgreeters;
+        package = package.xgreeters;
         name = "lightdm-tiny-greeter";
       };
 
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 47e60236eae..6ca7a4425f8 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -33,6 +33,8 @@ let
 
       # Implementation is done via pkgs/applications/display-managers/sddm/sddm-default-session.patch
       DefaultSession = optionalString (dmcfg.defaultSession != null) "${dmcfg.defaultSession}.desktop";
+
+      DisplayServer = if cfg.wayland.enable then "wayland" else "x11";
     };
 
     Theme = {
@@ -62,6 +64,7 @@ let
     Wayland = {
       EnableHiDPI = cfg.enableHidpi;
       SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
+      CompositorCommand = lib.optionalString cfg.wayland.enable cfg.wayland.compositorCommand;
     };
   } // lib.optionalAttrs dmcfg.autoLogin.enable {
     Autologin = {
@@ -184,6 +187,32 @@ in
           '';
         };
       };
+
+      # Experimental Wayland support
+      wayland = {
+        enable = mkEnableOption "experimental Wayland support";
+
+        compositorCommand = mkOption {
+          type = types.str;
+          internal = true;
+
+          # This is basically the upstream default, but with Weston referenced by full path
+          # and the configuration generated from NixOS options.
+          default = let westonIni = (pkgs.formats.ini {}).generate "weston.ini" {
+              libinput = {
+                enable-tap = xcfg.libinput.mouse.tapping;
+                left-handed = xcfg.libinput.mouse.leftHanded;
+              };
+              keyboard = {
+                keymap_model = xcfg.xkb.model;
+                keymap_layout = xcfg.xkb.layout;
+                keymap_variant = xcfg.xkb.variant;
+                keymap_options = xcfg.xkb.options;
+              };
+            }; in "${pkgs.weston}/bin/weston --shell=fullscreen-shell.so -c ${westonIni}";
+          description = lib.mdDoc "Command used to start the selected compositor";
+        };
+      };
     };
   };
 
diff --git a/nixos/modules/services/x11/display-managers/xpra.nix b/nixos/modules/services/x11/display-managers/xpra.nix
index cb78f52d9b6..0861530f21e 100644
--- a/nixos/modules/services/x11/display-managers/xpra.nix
+++ b/nixos/modules/services/x11/display-managers/xpra.nix
@@ -60,11 +60,11 @@ in
       VertRefresh 1.0 - 200.0
       #To add your own modes here, use a modeline calculator, like:
       # cvt:
-      # http://www.x.org/archive/X11R7.5/doc/man/man1/cvt.1.html
+      # https://www.x.org/archive/X11R7.5/doc/man/man1/cvt.1.html
       # xtiming:
-      # http://xtiming.sourceforge.net/cgi-bin/xtiming.pl
+      # https://xtiming.sourceforge.net/cgi-bin/xtiming.pl
       # gtf:
-      # http://gtf.sourceforge.net/
+      # https://gtf.sourceforge.net/
       #This can be used to get a specific DPI, but only for the default resolution:
       #DisplaySize 508 317
       #NOTE: the highest modes will not work without increasing the VideoRam
diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix
index 1f48713a68d..ab7e39739ee 100644
--- a/nixos/modules/services/x11/extra-layouts.nix
+++ b/nixos/modules/services/x11/extra-layouts.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  layouts = config.services.xserver.extraLayouts;
+  layouts = config.services.xserver.xkb.extraLayouts;
 
   layoutOpts = {
     options = {
@@ -15,10 +15,10 @@ let
       languages = mkOption {
         type = types.listOf types.str;
         description =
-        lib.mdDoc ''
-          A list of languages provided by the layout.
-          (Use ISO 639-2 codes, for example: "eng" for english)
-        '';
+          lib.mdDoc ''
+            A list of languages provided by the layout.
+            (Use ISO 639-2 codes, for example: "eng" for english)
+          '';
       };
 
       compatFile = mkOption {
@@ -80,29 +80,37 @@ let
   };
 
   xkb_patched = pkgs.xorg.xkeyboardconfig_custom {
-    layouts = config.services.xserver.extraLayouts;
+    layouts = config.services.xserver.xkb.extraLayouts;
   };
 
 in
 
 {
 
+  imports = [
+    (lib.mkRenamedOptionModuleWith {
+      sinceRelease = 2311;
+      from = [ "services" "xserver" "extraLayouts" ];
+      to = [ "services" "xserver" "xkb" "extraLayouts" ];
+    })
+  ];
+
   ###### interface
 
-  options.services.xserver = {
+  options.services.xserver.xkb = {
     extraLayouts = mkOption {
       type = types.attrsOf (types.submodule layoutOpts);
-      default = {};
+      default = { };
       example = literalExpression
-      ''
-        {
-          mine = {
-            description = "My custom xkb layout.";
-            languages = [ "eng" ];
-            symbolsFile = /path/to/my/layout;
-          };
-        }
-      '';
+        ''
+          {
+            mine = {
+              description = "My custom xkb layout.";
+              languages = [ "eng" ];
+              symbolsFile = /path/to/my/layout;
+            };
+          }
+        '';
       description = lib.mdDoc ''
         Extra custom layouts that will be included in the xkb configuration.
         Information on how to create a new layout can be found here:
@@ -121,11 +129,11 @@ in
     environment.sessionVariables = {
       # runtime override supported by multiple libraries e. g. libxkbcommon
       # https://xkbcommon.org/doc/current/group__include-path.html
-      XKB_CONFIG_ROOT = config.services.xserver.xkbDir;
+      XKB_CONFIG_ROOT = config.services.xserver.xkb.dir;
     };
 
     services.xserver = {
-      xkbDir = "${xkb_patched}/etc/X11/xkb";
+      xkb.dir = "${xkb_patched}/etc/X11/xkb";
       exportConfiguration = config.services.xserver.displayManager.startx.enable
         || config.services.xserver.displayManager.sx.enable;
     };
diff --git a/nixos/modules/services/x11/imwheel.nix b/nixos/modules/services/x11/imwheel.nix
index 133e64c65cd..bd2bcb7bcd0 100644
--- a/nixos/modules/services/x11/imwheel.nix
+++ b/nixos/modules/services/x11/imwheel.nix
@@ -37,7 +37,7 @@ in
             Window class translation rules.
             /etc/X11/imwheelrc is generated based on this config
             which means this config is global for all users.
-            See [official man pages](http://imwheel.sourceforge.net/imwheel.1.html)
+            See [official man pages](https://imwheel.sourceforge.net/imwheel.1.html)
             for more information.
           '';
         };
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index c2e6da4b453..4a8f2f61caa 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -175,6 +175,31 @@ in
         "Use services.xserver.fontPath instead of useXFS")
       (mkRemovedOptionModule [ "services" "xserver" "useGlamor" ]
         "Option services.xserver.useGlamor was removed because it is unnecessary. Drivers that uses Glamor will use it automatically.")
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "layout" ];
+        to = [ "services" "xserver" "xkb" "layout" ];
+      })
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "xkbModel" ];
+        to = [ "services" "xserver" "xkb" "model" ];
+      })
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "xkbOptions" ];
+        to = [ "services" "xserver" "xkb" "options" ];
+      })
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "xkbVariant" ];
+        to = [ "services" "xserver" "xkb" "variant" ];
+      })
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "xkbDir" ];
+        to = [ "services" "xserver" "xkb" "dir" ];
+      })
     ];
 
 
@@ -339,48 +364,50 @@ in
         '';
       };
 
-      layout = mkOption {
-        type = types.str;
-        default = "us";
-        description = lib.mdDoc ''
-          Keyboard layout, or multiple keyboard layouts separated by commas.
-        '';
-      };
+      xkb = {
+        layout = mkOption {
+          type = types.str;
+          default = "us";
+          description = lib.mdDoc ''
+            X keyboard layout, or multiple keyboard layouts separated by commas.
+          '';
+        };
 
-      xkbModel = mkOption {
-        type = types.str;
-        default = "pc104";
-        example = "presario";
-        description = lib.mdDoc ''
-          Keyboard model.
-        '';
-      };
+        model = mkOption {
+          type = types.str;
+          default = "pc104";
+          example = "presario";
+          description = lib.mdDoc ''
+            X keyboard model.
+          '';
+        };
 
-      xkbOptions = mkOption {
-        type = types.commas;
-        default = "terminate:ctrl_alt_bksp";
-        example = "grp:caps_toggle,grp_led:scroll";
-        description = lib.mdDoc ''
-          X keyboard options; layout switching goes here.
-        '';
-      };
+        options = mkOption {
+          type = types.commas;
+          default = "terminate:ctrl_alt_bksp";
+          example = "grp:caps_toggle,grp_led:scroll";
+          description = lib.mdDoc ''
+            X keyboard options; layout switching goes here.
+          '';
+        };
 
-      xkbVariant = mkOption {
-        type = types.str;
-        default = "";
-        example = "colemak";
-        description = lib.mdDoc ''
-          X keyboard variant.
-        '';
-      };
+        variant = mkOption {
+          type = types.str;
+          default = "";
+          example = "colemak";
+          description = lib.mdDoc ''
+            X keyboard variant.
+          '';
+        };
 
-      xkbDir = mkOption {
-        type = types.path;
-        default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
-        defaultText = literalExpression ''"''${pkgs.xkeyboard_config}/etc/X11/xkb"'';
-        description = lib.mdDoc ''
-          Path used for -xkbdir xserver parameter.
-        '';
+        dir = mkOption {
+          type = types.path;
+          default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
+          defaultText = literalExpression ''"''${pkgs.xkeyboard_config}/etc/X11/xkb"'';
+          description = lib.mdDoc ''
+            Path used for -xkbdir xserver parameter.
+          '';
+        };
       };
 
       config = mkOption {
@@ -667,7 +694,7 @@ in
         {
           "X11/xorg.conf".source = "${configFile}";
           # -xkbdir command line option does not seems to be passed to xkbcomp.
-          "X11/xkb".source = "${cfg.xkbDir}";
+          "X11/xkb".source = "${cfg.xkb.dir}";
         })
       # localectl looks into 00-keyboard.conf
       //{
@@ -675,10 +702,10 @@ in
             Section "InputClass"
               Identifier "Keyboard catchall"
               MatchIsKeyboard "on"
-              Option "XkbModel" "${cfg.xkbModel}"
-              Option "XkbLayout" "${cfg.layout}"
-              Option "XkbOptions" "${cfg.xkbOptions}"
-              Option "XkbVariant" "${cfg.xkbVariant}"
+              Option "XkbModel" "${cfg.xkb.model}"
+              Option "XkbLayout" "${cfg.xkb.layout}"
+              Option "XkbOptions" "${cfg.xkb.options}"
+              Option "XkbVariant" "${cfg.xkb.variant}"
             EndSection
           '';
         }
@@ -759,7 +786,7 @@ in
 
     services.xserver.displayManager.xserverArgs =
       [ "-config ${configFile}"
-        "-xkbdir" "${cfg.xkbDir}"
+        "-xkbdir" "${cfg.xkb.dir}"
       ] ++ optional (cfg.display != null) ":${toString cfg.display}"
         ++ optional (cfg.tty     != null) "vt${toString cfg.tty}"
         ++ optional (cfg.dpi     != null) "-dpi ${toString cfg.dpi}"
@@ -777,14 +804,14 @@ in
       ];
 
     system.checks = singleton (pkgs.runCommand "xkb-validated" {
-      inherit (cfg) xkbModel layout xkbVariant xkbOptions;
+      inherit (cfg.xkb) model layout variant options;
       nativeBuildInputs = with pkgs.buildPackages; [ xkbvalidate ];
       preferLocalBuild = true;
     } ''
       ${optionalString (config.environment.sessionVariables ? XKB_CONFIG_ROOT)
         "export XKB_CONFIG_ROOT=${config.environment.sessionVariables.XKB_CONFIG_ROOT}"
       }
-      xkbvalidate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
+      xkbvalidate "$model" "$layout" "$variant" "$options"
       touch "$out"
     '');
 
diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix
index 7f6154794bd..3d941596747 100644
--- a/nixos/modules/system/activation/activatable-system.nix
+++ b/nixos/modules/system/activation/activatable-system.nix
@@ -1,52 +1,16 @@
-{ config, lib, pkgs, ... }:
+{ options, config, lib, pkgs, ... }:
 
 let
   inherit (lib)
     mkOption
-    optionalString
     types
     ;
 
-  perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
-
   systemBuilderArgs = {
     activationScript = config.system.activationScripts.script;
     dryActivationScript = config.system.dryActivationScript;
   };
 
-  systemBuilderCommands = ''
-    echo "$activationScript" > $out/activate
-    echo "$dryActivationScript" > $out/dry-activate
-    substituteInPlace $out/activate --subst-var-by out ''${!toplevelVar}
-    substituteInPlace $out/dry-activate --subst-var-by out ''${!toplevelVar}
-    chmod u+x $out/activate $out/dry-activate
-    unset activationScript dryActivationScript
-
-    mkdir $out/bin
-    substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \
-      --subst-var out \
-      --subst-var-by toplevel ''${!toplevelVar} \
-      --subst-var-by coreutils "${pkgs.coreutils}" \
-      --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \
-      --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \
-      --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \
-      --subst-var-by perl "${perlWrapped}" \
-      --subst-var-by shell "${pkgs.bash}/bin/sh" \
-      --subst-var-by su "${pkgs.shadow.su}/bin/su" \
-      --subst-var-by systemd "${config.systemd.package}" \
-      --subst-var-by utillinux "${pkgs.util-linux}" \
-      ;
-
-    chmod +x $out/bin/switch-to-configuration
-    ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
-      if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then
-        echo "switch-to-configuration syntax is not valid:"
-        echo "$output"
-        exit 1
-      fi
-    ''}
-  '';
-
 in
 {
   options = {
@@ -60,6 +24,18 @@ in
         do, but for image based systems, this may not be needed or not be desirable.
       '';
     };
+    system.activatableSystemBuilderCommands = options.system.systemBuilderCommands // {
+      description = lib.mdDoc ''
+        Like `system.systemBuilderCommands`, but only for the commands that are
+        needed *both* when the system is activatable and when it isn't.
+
+        Disclaimer: This option might go away in the future. It might be
+        superseded by separating switch-to-configuration into a separate script
+        which will make this option superfluous. See
+        https://github.com/NixOS/nixpkgs/pull/263462#discussion_r1373104845 for
+        a discussion.
+      '';
+    };
     system.build.separateActivationScript = mkOption {
       type = types.package;
       description = ''
@@ -71,7 +47,18 @@ in
     };
   };
   config = {
-    system.systemBuilderCommands = lib.mkIf config.system.activatable systemBuilderCommands;
+    system.activatableSystemBuilderCommands = ''
+      echo "$activationScript" > $out/activate
+      echo "$dryActivationScript" > $out/dry-activate
+      substituteInPlace $out/activate --subst-var-by out ''${!toplevelVar}
+      substituteInPlace $out/dry-activate --subst-var-by out ''${!toplevelVar}
+      chmod u+x $out/activate $out/dry-activate
+      unset activationScript dryActivationScript
+    '';
+
+    system.systemBuilderCommands = lib.mkIf
+      config.system.activatable
+      config.system.activatableSystemBuilderCommands;
     system.systemBuilderArgs = lib.mkIf config.system.activatable
       (systemBuilderArgs // {
         toplevelVar = "out";
@@ -86,7 +73,7 @@ in
         })
         ''
           mkdir $out
-          ${systemBuilderCommands}
+          ${config.system.activatableSystemBuilderCommands}
         '';
   };
 }
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index c8407dd6779..bc0b7266ce9 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -55,10 +55,6 @@ let
       # used as a garbage collection root.
       ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
 
-      # Prevent the current configuration from being garbage-collected.
-      mkdir -p /nix/var/nix/gcroots
-      ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
-
       exit $_status
     '';
 
@@ -233,23 +229,15 @@ in
   config = {
 
     system.activationScripts.stdio = ""; # obsolete
+    system.activationScripts.var = ""; # obsolete
 
-    system.activationScripts.var =
-      ''
-        # Various log/runtime directories.
-
-        mkdir -p /var/tmp
-        chmod 1777 /var/tmp
-
-        # Empty, immutable home directory of many system accounts.
-        mkdir -p /var/empty
-        # Make sure it's really empty
-        ${pkgs.e2fsprogs}/bin/chattr -f -i /var/empty || true
-        find /var/empty -mindepth 1 -delete
-        chmod 0555 /var/empty
-        chown root:root /var/empty
-        ${pkgs.e2fsprogs}/bin/chattr -f +i /var/empty || true
-      '';
+    systemd.tmpfiles.rules = [
+      # Prevent the current configuration from being garbage-collected.
+      "d /nix/var/nix/gcroots -"
+      "L+ /nix/var/nix/gcroots/current-system - - - - /run/current-system"
+      "D /var/empty 0555 root root -"
+      "h /var/empty - - - - +i"
+    ];
 
     system.activationScripts.usrbinenv = if config.environment.usrbinenv != null
       then ''
diff --git a/nixos/modules/system/activation/bootspec.nix b/nixos/modules/system/activation/bootspec.nix
index 9e1fa309d5d..98c234bc340 100644
--- a/nixos/modules/system/activation/bootspec.nix
+++ b/nixos/modules/system/activation/bootspec.nix
@@ -79,7 +79,7 @@ in
       // { default = true; internal = true; };
     enableValidation = lib.mkEnableOption (lib.mdDoc ''the validation of bootspec documents for each build.
       This will introduce Go in the build-time closure as we are relying on [Cuelang](https://cuelang.org/) for schema validation.
-      Enable this option if you want to ascertain that your documents are correct.
+      Enable this option if you want to ascertain that your documents are correct
       ''
     );
 
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index e05f89bb0fb..e2f66a287bc 100755
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -22,6 +22,7 @@ use JSON::PP;
 use IPC::Cmd;
 use Sys::Syslog qw(:standard :macros);
 use Cwd qw(abs_path);
+use Fcntl ':flock';
 
 ## no critic(ControlStructures::ProhibitDeepNests)
 ## no critic(ErrorHandling::RequireCarping)
@@ -91,6 +92,8 @@ if (!-f "/etc/NIXOS" && (read_file("/etc/os-release", err_mode => "quiet") // ""
 }
 
 make_path("/run/nixos", { mode => oct(755) });
+open(my $stc_lock, '>>', '/run/nixos/switch-to-configuration.lock') or die "Could not open lock - $!";
+flock($stc_lock, LOCK_EX) or die "Could not acquire lock - $!";
 openlog("nixos", "", LOG_USER);
 
 # Install or update the bootloader.
@@ -599,7 +602,9 @@ while (my ($unit, $state) = each(%{$active_cur})) {
                     $units_to_start{$unit} = 1;
                     record_unit($start_list_file, $unit);
                     # Don't spam the user with target units that always get started.
-                    $units_to_filter{$unit} = 1;
+                    if (($ENV{"STC_DISPLAY_ALL_UNITS"} // "") ne "1") {
+                        $units_to_filter{$unit} = 1;
+                    }
                 }
             }
 
@@ -983,4 +988,5 @@ if ($res == 0) {
     syslog(LOG_ERR, "switching to system configuration $toplevel failed (status $res)");
 }
 
+close($stc_lock) or die "Could not close lock - $!";
 exit($res);
diff --git a/nixos/modules/system/activation/switchable-system.nix b/nixos/modules/system/activation/switchable-system.nix
new file mode 100644
index 00000000000..00bc18e48d1
--- /dev/null
+++ b/nixos/modules/system/activation/switchable-system.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
+
+in
+
+{
+
+  options = {
+    system.switch.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Whether to include the capability to switch configurations.
+
+        Disabling this makes the system unable to be reconfigured via `nixos-rebuild`.
+
+        This is good for image based appliances where updates are handled
+        outside the image. Reducing features makes the image lighter and
+        slightly more secure.
+      '';
+    };
+  };
+
+  config = lib.mkIf config.system.switch.enable {
+    system.activatableSystemBuilderCommands = ''
+      mkdir $out/bin
+      substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \
+        --subst-var out \
+        --subst-var-by toplevel ''${!toplevelVar} \
+        --subst-var-by coreutils "${pkgs.coreutils}" \
+        --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \
+        --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \
+        --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \
+        --subst-var-by perl "${perlWrapped}" \
+        --subst-var-by shell "${pkgs.bash}/bin/sh" \
+        --subst-var-by su "${pkgs.shadow.su}/bin/su" \
+        --subst-var-by systemd "${config.systemd.package}" \
+        --subst-var-by utillinux "${pkgs.util-linux}" \
+        ;
+
+      chmod +x $out/bin/switch-to-configuration
+      ${lib.optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
+        if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then
+          echo "switch-to-configuration syntax is not valid:"
+          echo "$output"
+          exit 1
+        fi
+      ''}
+    '';
+  };
+
+}
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 8c9483f01c1..d16152ab9de 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -20,17 +20,13 @@ let
                  optionalString fixBinary "F";
   in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
 
-  activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then ''
-    rm -f /run/binfmt/${name}
-    cat > /run/binfmt/${name} << 'EOF'
-    #!${pkgs.bash}/bin/sh
-    exec -- ${interpreter} "$@"
-    EOF
-    chmod +x /run/binfmt/${name}
-  '' else ''
-    rm -f /run/binfmt/${name}
-    ln -s ${interpreter} /run/binfmt/${name}
-  '';
+  mkInterpreter = name: { interpreter, wrapInterpreterInShell, ... }:
+    if wrapInterpreterInShell
+    then pkgs.writeShellScript "${name}-interpreter" ''
+           #!${pkgs.bash}/bin/sh
+           exec -- ${interpreter} "$@"
+         ''
+    else interpreter;
 
   getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
   getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch;
@@ -318,18 +314,25 @@ in {
 
     environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
       (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
-    system.activationScripts.binfmt = stringAfter [ "specialfs" ] ''
-      mkdir -p /run/binfmt
-      chmod 0755 /run/binfmt
-      ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
-    '';
-    systemd = lib.mkIf (config.boot.binfmt.registrations != {}) {
-      additionalUpstreamSystemUnits = [
-        "proc-sys-fs-binfmt_misc.automount"
-        "proc-sys-fs-binfmt_misc.mount"
-        "systemd-binfmt.service"
-      ];
-      services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
-    };
+
+    systemd = lib.mkMerge [
+      ({ tmpfiles.rules = [
+          "d /run/binfmt 0755 -"
+        ] ++ lib.mapAttrsToList
+          (name: interpreter:
+            "L+ /run/binfmt/${name} - - - - ${interpreter}"
+          )
+          (lib.mapAttrs mkInterpreter config.boot.binfmt.registrations);
+      })
+
+      (lib.mkIf (config.boot.binfmt.registrations != {}) {
+        additionalUpstreamSystemUnits = [
+          "proc-sys-fs-binfmt_misc.automount"
+          "proc-sys-fs-binfmt_misc.mount"
+          "systemd-binfmt.service"
+        ];
+        services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
+      })
+    ];
   };
 }
diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix
index a2764187a53..897602f9826 100644
--- a/nixos/modules/system/boot/grow-partition.nix
+++ b/nixos/modules/system/boot/grow-partition.nix
@@ -12,33 +12,32 @@ with lib;
   ];
 
   options = {
-    boot.growPartition = mkEnableOption (lib.mdDoc "grow the root partition on boot");
+    boot.growPartition = mkEnableOption (lib.mdDoc "growing the root partition on boot");
   };
 
   config = mkIf config.boot.growPartition {
-
-    assertions = [{
-      assertion = !config.boot.initrd.systemd.enable;
-      message = "systemd stage 1 does not support 'boot.growPartition' yet.";
-    }];
-
-    boot.initrd.extraUtilsCommands = ''
-      copy_bin_and_libs ${pkgs.gawk}/bin/gawk
-      copy_bin_and_libs ${pkgs.gnused}/bin/sed
-      copy_bin_and_libs ${pkgs.util-linux}/sbin/sfdisk
-      copy_bin_and_libs ${pkgs.util-linux}/sbin/lsblk
-
-      substitute "${pkgs.cloud-utils.guest}/bin/.growpart-wrapped" "$out/bin/growpart" \
-        --replace "${pkgs.bash}/bin/sh" "/bin/sh" \
-        --replace "awk" "gawk" \
-        --replace "sed" "gnused"
-
-      ln -s sed $out/bin/gnused
-    '';
-
-    boot.initrd.postDeviceCommands = ''
-      rootDevice="${config.fileSystems."/".device}"
-      if waitDevice "$rootDevice"; then
+    assertions = [
+      {
+        assertion = !config.boot.initrd.systemd.repart.enable && !config.systemd.repart.enable;
+        message = "systemd-repart already grows the root partition and thus you should not use boot.growPartition";
+      }
+    ];
+    systemd.services.growpart = {
+      wantedBy = [ "-.mount" ];
+      after = [ "-.mount" ];
+      before = [ "systemd-growfs-root.service" ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        TimeoutSec = "infinity";
+        # growpart returns 1 if the partition is already grown
+        SuccessExitStatus = "0 1";
+      };
+
+      script = ''
+        rootDevice="${config.fileSystems."/".device}"
         rootDevice="$(readlink -f "$rootDevice")"
         parentDevice="$rootDevice"
         while [ "''${parentDevice%[0-9]}" != "''${parentDevice}" ]; do
@@ -48,11 +47,8 @@ with lib;
         if [ "''${parentDevice%[0-9]p}" != "''${parentDevice}" ] && [ -b "''${parentDevice%p}" ]; then
           parentDevice="''${parentDevice%p}"
         fi
-        TMPDIR=/run sh $(type -P growpart) "$parentDevice" "$partNum"
-        udevadm settle
-      fi
-    '';
-
+        "${pkgs.cloud-utils.guest}/bin/growpart" "$parentDevice" "$partNum"
+      '';
+    };
   };
-
 }
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
index 1d95742face..88ba43caf00 100644
--- a/nixos/modules/system/boot/initrd-network.nix
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -80,7 +80,7 @@ in
     };
 
     boot.initrd.network.udhcpc.enable = mkOption {
-      default = config.networking.useDHCP;
+      default = config.networking.useDHCP && !config.boot.initrd.systemd.enable;
       defaultText = "networking.useDHCP";
       type = types.bool;
       description = lib.mdDoc ''
@@ -116,11 +116,11 @@ in
 
     boot.initrd.kernelModules = [ "af_packet" ];
 
-    boot.initrd.extraUtilsCommands = ''
+    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.klibc}/lib/klibc/bin.static/ipconfig
     '';
 
-    boot.initrd.preLVMCommands = mkBefore (
+    boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore (
       # Search for interface definitions in command line.
       ''
         ifaces=""
@@ -138,7 +138,7 @@ in
         # Bring up all interfaces.
         for iface in ${dhcpIfShellExpr}; do
           echo "bringing up network interface $iface..."
-          ip link set "$iface" up && ifaces="$ifaces $iface"
+          ip link set dev "$iface" up && ifaces="$ifaces $iface"
         done
 
         # Acquire DHCP leases.
@@ -148,12 +148,12 @@ in
         done
       ''
 
-      + cfg.postCommands);
+      + cfg.postCommands));
 
-    boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 ''
+    boot.initrd.postMountCommands = mkIf (cfg.flushBeforeStage2 && !config.boot.initrd.systemd.enable) ''
       for iface in $ifaces; do
-        ip address flush "$iface"
-        ip link set "$iface" down
+        ip address flush dev "$iface"
+        ip link set dev "$iface" down
       done
     '';
 
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 60c5ff62fff..a8cd2e8f05f 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -164,13 +164,12 @@ in
           for instructions.
         '';
       }
-
-      {
-        assertion = config.boot.initrd.systemd.enable -> cfg.shell == null;
-        message = "systemd stage 1 does not support boot.initrd.network.ssh.shell";
-      }
     ];
 
+    warnings = lib.optional (config.boot.initrd.systemd.enable && cfg.shell != null) ''
+      Please set 'boot.initrd.systemd.users.root.shell' instead of 'boot.initrd.network.ssh.shell'
+    '';
+
     boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${package}/bin/sshd
       cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
@@ -235,6 +234,8 @@ in
       users.sshd = { uid = 1; group = "sshd"; };
       groups.sshd = { gid = 1; };
 
+      users.root.shell = mkIf (config.boot.initrd.network.ssh.shell != null) config.boot.initrd.network.ssh.shell;
+
       contents."/etc/ssh/authorized_keys.d/root".text =
         concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys;
       contents."/etc/ssh/sshd_config".text = sshdConfig;
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index 9ea61191967..a46331ccd43 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -96,8 +96,8 @@ in
                                         # (required, but can be null if only config changes
                                         # are needed)
 
-          extraStructuredConfig = {     # attrset of extra configuration parameters
-            FOO = lib.kernel.yes;       # (without the CONFIG_ prefix, optional)
+          extraStructuredConfig = {     # attrset of extra configuration parameters without the CONFIG_ prefix
+            FOO = lib.kernel.yes;       # (optional)
           };                            # values should generally be lib.kernel.yes,
                                         # lib.kernel.no or lib.kernel.module
 
@@ -105,8 +105,9 @@ in
             foo = true;                 # (may be checked by other NixOS modules, optional)
           };
 
-          extraConfig = "CONFIG_FOO y"; # extra configuration options in string form
-                                        # (deprecated, use extraStructuredConfig instead, optional)
+          extraConfig = "FOO y";        # extra configuration options in string form without the CONFIG_ prefix
+                                        # (optional, multiple lines allowed to specify multiple options)
+                                        # (deprecated, use extraStructuredConfig instead)
         }
         ```
 
@@ -269,6 +270,9 @@ in
             "ata_piix"
             "pata_marvell"
 
+            # NVMe
+            "nvme"
+
             # Standard SCSI stuff.
             "sd_mod"
             "sr_mod"
diff --git a/nixos/modules/system/boot/loader/external/external.nix b/nixos/modules/system/boot/loader/external/external.nix
index 926cbd2b4b3..78982356a9e 100644
--- a/nixos/modules/system/boot/loader/external/external.nix
+++ b/nixos/modules/system/boot/loader/external/external.nix
@@ -12,7 +12,7 @@ in
   };
 
   options.boot.loader.external = {
-    enable = mkEnableOption (lib.mdDoc "use an external tool to install your bootloader");
+    enable = mkEnableOption (lib.mdDoc "using an external tool to install your bootloader");
 
     installHook = mkOption {
       type = with types; path;
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
index 5ef3c5cd52a..13df6090711 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -25,7 +25,7 @@ in
           under `/boot/extlinux.conf`.  For instance,
           U-Boot's generic distro boot support uses this file format.
 
-          See [U-boot's documentation](http://git.denx.de/?p=u-boot.git;a=blob;f=doc/README.distro;hb=refs/heads/master)
+          See [U-boot's documentation](https://u-boot.readthedocs.io/en/latest/develop/distro.html)
           for more information.
         '';
       };
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 468f701ae5b..7097e1d83dc 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -339,7 +339,7 @@ in
           See the
           [
           GRUB source code
-          ](http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326)
+          ](https://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326)
           for which disk modules are available.
 
           The list elements are passed directly as `argv`
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 a040518a5a5..e2e7ffe59dc 100755..100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -1,27 +1,41 @@
 #! @python3@/bin/python3 -B
 import argparse
-import shutil
-import os
-import sys
-import errno
-import subprocess
-import glob
-import tempfile
-import errno
-import warnings
 import ctypes
-libc = ctypes.CDLL("libc.so.6")
-import re
 import datetime
+import errno
 import glob
+import os
 import os.path
-from typing import NamedTuple, List, Optional
-from packaging import version
+import re
+import shutil
+import subprocess
+import sys
+import warnings
+import json
+from typing import NamedTuple, Dict, List
+from dataclasses import dataclass
+
+
+@dataclass
+class BootSpec:
+    init: str
+    initrd: str
+    initrdSecrets: str
+    kernel: str
+    kernelParams: List[str]
+    label: str
+    system: str
+    toplevel: str
+    specialisations: Dict[str, "BootSpec"]
+
+
+
+libc = ctypes.CDLL("libc.so.6")
 
 class SystemIdentifier(NamedTuple):
-    profile: Optional[str]
+    profile: str | None
     generation: int
-    specialisation: Optional[str]
+    specialisation: str | None
 
 
 def copy_if_not_exists(source: str, dest: str) -> None:
@@ -29,13 +43,13 @@ def copy_if_not_exists(source: str, dest: str) -> None:
         shutil.copyfile(source, dest)
 
 
-def generation_dir(profile: Optional[str], generation: int) -> str:
+def generation_dir(profile: str | None, generation: int) -> str:
     if profile:
         return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation)
     else:
         return "/nix/var/nix/profiles/system-%d-link" % (generation)
 
-def system_dir(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
+def system_dir(profile: str | None, generation: int, specialisation: str | None) -> str:
     d = generation_dir(profile, generation)
     if specialisation:
         return os.path.join(d, "specialisation", specialisation)
@@ -49,7 +63,7 @@ initrd {initrd}
 options {kernel_params}
 """
 
-def generation_conf_filename(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
+def generation_conf_filename(profile: str | None, generation: int, specialisation: str | None) -> str:
     pieces = [
         "nixos",
         profile or None,
@@ -60,23 +74,44 @@ def generation_conf_filename(profile: Optional[str], generation: int, specialisa
     return "-".join(p for p in pieces if p) + ".conf"
 
 
-def write_loader_conf(profile: Optional[str], generation: int, specialisation: Optional[str]) -> None:
+def write_loader_conf(profile: str | None, generation: int, specialisation: str | None) -> None:
     with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
         if "@timeout@" != "":
             f.write("timeout @timeout@\n")
         f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation))
         if not @editor@:
-            f.write("editor 0\n");
-        f.write("console-mode @consoleMode@\n");
+            f.write("editor 0\n")
+        f.write("console-mode @consoleMode@\n")
+        f.flush()
+        os.fsync(f.fileno())
     os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
 
 
-def profile_path(profile: Optional[str], generation: int, specialisation: Optional[str], name: str) -> str:
-    return os.path.realpath("%s/%s" % (system_dir(profile, generation, specialisation), name))
+def get_bootspec(profile: str | None, generation: int) -> BootSpec:
+    system_directory = system_dir(profile, generation, None)
+    boot_json_path = os.path.realpath("%s/%s" % (system_directory, "boot.json"))
+    if os.path.isfile(boot_json_path):
+        boot_json_f = open(boot_json_path, 'r')
+        bootspec_json = json.load(boot_json_f)
+    else:
+        boot_json_str = subprocess.check_output([
+        "@bootspecTools@/bin/synthesize",
+        "--version",
+        "1",
+        system_directory,
+        "/dev/stdout"],
+        universal_newlines=True)
+        bootspec_json = json.loads(boot_json_str)
+    return bootspec_from_json(bootspec_json)
 
+def bootspec_from_json(bootspec_json: Dict) -> BootSpec:
+    specialisations = bootspec_json['org.nixos.specialisation.v1']
+    specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()}
+    return BootSpec(**bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations)
 
-def copy_from_profile(profile: Optional[str], generation: int, specialisation: Optional[str], name: str, dry_run: bool = False) -> str:
-    store_file_path = profile_path(profile, generation, specialisation, name)
+
+def copy_from_file(file: str, dry_run: bool = False) -> str:
+    store_file_path = os.path.realpath(file)
     suffix = os.path.basename(store_file_path)
     store_dir = os.path.basename(os.path.dirname(store_file_path))
     efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
@@ -84,40 +119,19 @@ def copy_from_profile(profile: Optional[str], generation: int, specialisation: O
         copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
     return efi_file_path
 
-
-def describe_generation(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
-    try:
-        with open(profile_path(profile, generation, specialisation, "nixos-version")) as f:
-            nixos_version = f.read()
-    except IOError:
-        nixos_version = "Unknown"
-
-    kernel_dir = os.path.dirname(profile_path(profile, generation, specialisation, "kernel"))
-    module_dir = glob.glob("%s/lib/modules/*" % kernel_dir)[0]
-    kernel_version = os.path.basename(module_dir)
-
-    build_time = int(os.path.getctime(system_dir(profile, generation, specialisation)))
-    build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
-
-    description = "@distroName@ {}, Linux Kernel {}, Built on {}".format(
-        nixos_version, kernel_version, build_date
-    )
-
-    return description
-
-
-def write_entry(profile: Optional[str], generation: int, specialisation: Optional[str],
-                machine_id: str, current: bool) -> None:
-    kernel = copy_from_profile(profile, generation, specialisation, "kernel")
-    initrd = copy_from_profile(profile, generation, specialisation, "initrd")
+def write_entry(profile: str | None, generation: int, specialisation: str | None,
+                machine_id: str, bootspec: BootSpec, current: bool) -> None:
+    if specialisation:
+        bootspec = bootspec.specialisations[specialisation]
+    kernel = copy_from_file(bootspec.kernel)
+    initrd = copy_from_file(bootspec.initrd)
 
     title = "@distroName@{profile}{specialisation}".format(
         profile=" [" + profile + "]" if profile else "",
         specialisation=" (%s)" % specialisation if specialisation else "")
 
     try:
-        append_initrd_secrets = profile_path(profile, generation, specialisation, "append-initrd-secrets")
-        subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
+        subprocess.check_call([bootspec.initrdSecrets, "@efiSysMountPoint@%s" % (initrd)])
     except FileNotFoundError:
         pass
     except subprocess.CalledProcessError:
@@ -132,31 +146,27 @@ def write_entry(profile: Optional[str], generation: int, specialisation: Optiona
     entry_file = "@efiSysMountPoint@/loader/entries/%s" % (
         generation_conf_filename(profile, generation, specialisation))
     tmp_path = "%s.tmp" % (entry_file)
-    kernel_params = "init=%s " % profile_path(profile, generation, specialisation, "init")
+    kernel_params = "init=%s " % bootspec.init
+
+    kernel_params = kernel_params + " ".join(bootspec.kernelParams)
+    build_time = int(os.path.getctime(system_dir(profile, generation, specialisation)))
+    build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
 
-    with open(profile_path(profile, generation, specialisation, "kernel-params")) as params_file:
-        kernel_params = kernel_params + params_file.read()
     with open(tmp_path, 'w') as f:
         f.write(BOOT_ENTRY.format(title=title,
                     generation=generation,
                     kernel=kernel,
                     initrd=initrd,
                     kernel_params=kernel_params,
-                    description=describe_generation(profile, generation, specialisation)))
+                    description=f"{bootspec.label}, built on {build_date}"))
         if machine_id is not None:
             f.write("machine-id %s\n" % machine_id)
+        f.flush()
+        os.fsync(f.fileno())
     os.rename(tmp_path, entry_file)
 
 
-def mkdir_p(path: str) -> None:
-    try:
-        os.makedirs(path)
-    except OSError as e:
-        if e.errno != errno.EEXIST or not os.path.isdir(path):
-            raise
-
-
-def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]:
+def get_generations(profile: str | None = None) -> list[SystemIdentifier]:
     gen_list = subprocess.check_output([
         "@nix@/bin/nix-env",
         "--list-generations",
@@ -179,21 +189,14 @@ def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]:
     return configurations[-configurationLimit:]
 
 
-def get_specialisations(profile: Optional[str], generation: int, _: Optional[str]) -> List[SystemIdentifier]:
-    specialisations_dir = os.path.join(
-            system_dir(profile, generation, None), "specialisation")
-    if not os.path.exists(specialisations_dir):
-        return []
-    return [SystemIdentifier(profile, generation, spec) for spec in os.listdir(specialisations_dir)]
-
-
-def remove_old_entries(gens: List[SystemIdentifier]) -> None:
-    rex_profile = re.compile("^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
-    rex_generation = re.compile("^@efiSysMountPoint@/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
+def remove_old_entries(gens: list[SystemIdentifier]) -> None:
+    rex_profile = re.compile(r"^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
+    rex_generation = re.compile(r"^@efiSysMountPoint@/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
     known_paths = []
     for gen in gens:
-        known_paths.append(copy_from_profile(*gen, "kernel", True))
-        known_paths.append(copy_from_profile(*gen, "initrd", True))
+        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("@efiSysMountPoint@/loader/entries/nixos*-generation-[1-9]*.conf"):
         if rex_profile.match(path):
             prof = rex_profile.sub(r"\1", path)
@@ -210,7 +213,7 @@ def remove_old_entries(gens: List[SystemIdentifier]) -> None:
             os.unlink(path)
 
 
-def get_profiles() -> List[str]:
+def get_profiles() -> list[str]:
     if os.path.isdir("/nix/var/nix/profiles/system-profiles/"):
         return [x
             for x in os.listdir("/nix/var/nix/profiles/system-profiles/")
@@ -218,11 +221,7 @@ def get_profiles() -> List[str]:
     else:
         return []
 
-def main() -> None:
-    parser = argparse.ArgumentParser(description='Update @distroName@-related systemd-boot files')
-    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default @distroName@ config to boot')
-    args = parser.parse_args()
-
+def install_bootloader(args: argparse.Namespace) -> None:
     try:
         with open("/etc/machine-id") as machine_file:
             machine_id = machine_file.readlines()[0]
@@ -273,21 +272,15 @@ def main() -> None:
         if available_match is None:
             raise Exception("could not determine systemd-boot version")
 
-        installed_version = version.parse(installed_match.group(1))
-        available_version = version.parse(available_match.group(1))
+        installed_version = installed_match.group(1)
+        available_version = available_match.group(1)
 
-        # systemd 252 has a regression that leaves some machines unbootable, so we skip that update.
-        # The fix is in 252.2
-        # See https://github.com/systemd/systemd/issues/25363 and https://github.com/NixOS/nixpkgs/pull/201558#issuecomment-1348603263
         if installed_version < available_version:
-            if version.parse('252') <= available_version < version.parse('252.2'):
-                print("skipping systemd-boot update to %s because of known regression" % available_version)
-            else:
-                print("updating systemd-boot from %s to %s" % (installed_version, available_version))
-                subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@"] + bootctl_flags + ["update"])
+            print("updating systemd-boot from %s to %s" % (installed_version, available_version))
+            subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@"] + bootctl_flags + ["update"])
 
-    mkdir_p("@efiSysMountPoint@/efi/nixos")
-    mkdir_p("@efiSysMountPoint@/loader/entries")
+    os.makedirs("@efiSysMountPoint@/efi/nixos", exist_ok=True)
+    os.makedirs("@efiSysMountPoint@/loader/entries", exist_ok=True)
 
     gens = get_generations()
     for profile in get_profiles():
@@ -295,10 +288,11 @@ def main() -> None:
     remove_old_entries(gens)
     for gen in gens:
         try:
-            is_default = os.path.dirname(profile_path(*gen, "init")) == args.default_config
-            write_entry(*gen, machine_id, current=is_default)
-            for specialisation in get_specialisations(*gen):
-                write_entry(*specialisation, machine_id, current=is_default)
+            bootspec = get_bootspec(gen.profile, gen.generation)
+            is_default = os.path.dirname(bootspec.init) == args.default_config
+            write_entry(*gen, machine_id, bootspec, current=is_default)
+            for specialisation in bootspec.specialisations.keys():
+                write_entry(gen.profile, gen.generation, specialisation, machine_id, bootspec, current=is_default)
             if is_default:
                 write_loader_conf(*gen)
         except OSError as e:
@@ -324,17 +318,26 @@ def main() -> None:
             os.rmdir(actual_root)
         os.rmdir(root)
 
-    mkdir_p("@efiSysMountPoint@/efi/nixos/.extra-files")
+    os.makedirs("@efiSysMountPoint@/efi/nixos/.extra-files", exist_ok=True)
 
     subprocess.check_call("@copyExtraFiles@")
 
-    # Since fat32 provides little recovery facilities after a crash,
-    # it can leave the system in an unbootable state, when a crash/outage
-    # happens shortly after an update. To decrease the likelihood of this
-    # event sync the efi filesystem after each update.
-    rc = libc.syncfs(os.open("@efiSysMountPoint@", os.O_RDONLY))
-    if rc != 0:
-        print("could not sync @efiSysMountPoint@: {}".format(os.strerror(rc)), file=sys.stderr)
+
+def main() -> None:
+    parser = argparse.ArgumentParser(description='Update @distroName@-related systemd-boot files')
+    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default @distroName@ config to boot')
+    args = parser.parse_args()
+
+    try:
+        install_bootloader(args)
+    finally:
+        # Since fat32 provides little recovery facilities after a crash,
+        # it can leave the system in an unbootable state, when a crash/outage
+        # happens shortly after an update. To decrease the likelihood of this
+        # event sync the efi filesystem after each update.
+        rc = libc.syncfs(os.open("@efiSysMountPoint@", os.O_RDONLY))
+        if rc != 0:
+            print("could not sync @efiSysMountPoint@: {}".format(os.strerror(rc)), file=sys.stderr)
 
 
 if __name__ == '__main__':
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index d9a1535ffc7..9d55c21077d 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -7,17 +7,17 @@ let
 
   efi = config.boot.loader.efi;
 
-  python3 = pkgs.python3.withPackages (ps: [ ps.packaging ]);
-
   systemdBootBuilder = pkgs.substituteAll {
     src = ./systemd-boot-builder.py;
 
     isExecutable = true;
 
-    inherit python3;
+    inherit (pkgs) python3;
 
     systemd = config.systemd.package;
 
+    bootspecTools = pkgs.bootspec;
+
     nix = config.nix.package.out;
 
     timeout = optionalString (config.boot.loader.timeout != null) config.boot.loader.timeout;
@@ -52,7 +52,7 @@ let
   };
 
   checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" {
-    nativeBuildInputs = [ pkgs.mypy python3 ];
+    nativeBuildInputs = [ pkgs.mypy ];
   } ''
     install -m755 ${systemdBootBuilder} $out
     mypy \
@@ -69,6 +69,8 @@ let
   '';
 in {
 
+  meta.maintainers = with lib.maintainers; [ julienmalka ];
+
   imports =
     [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ])
     ];
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 06c329e006b..ca560d63f3b 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -537,7 +537,7 @@ in
       description = lib.mdDoc ''
         Unless enabled, encryption keys can be easily recovered by an attacker with physical
         access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
-        More information is available at <http://en.wikipedia.org/wiki/DMA_attack>.
+        More information is available at <https://en.wikipedia.org/wiki/DMA_attack>.
 
         This option blacklists FireWire drivers, but doesn't remove them. You can manually
         load the drivers if you need to use a FireWire device, but don't forget to unload them!
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 238c6670ea0..33261021480 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -83,7 +83,7 @@ let
         (assertByteFormat "BitsPerSecond")
         (assertValueOneOf "Duplex" ["half" "full"])
         (assertValueOneOf "AutoNegotiation" boolValues)
-        (assertValueOneOf "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon" "off"])
+        (assertValuesSomeOfOr "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon"] "off")
         (assertValueOneOf "Port" ["tp" "aui" "bnc" "mii" "fibre"])
         (assertValueOneOf "ReceiveChecksumOffload" boolValues)
         (assertValueOneOf "TransmitChecksumOffload" boolValues)
@@ -122,6 +122,16 @@ let
         (assertValueOneOf "PacketInfo" boolValues)
         (assertValueOneOf "VNetHeader" boolValues)
       ];
+
+      # See https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#%5BIPVTAP%5D%20Section%20Options
+      ipVlanVtapChecks = [
+        (assertOnlyFields [
+          "Mode"
+          "Flags"
+        ])
+        (assertValueOneOf "Mode" ["L2" "L3" "L3S" ])
+        (assertValueOneOf "Flags" ["private" "vepa" "bridge" ])
+      ];
     in {
 
       sectionNetdev = checkUnitConfig "Netdev" [
@@ -146,6 +156,7 @@ let
           "ip6gretap"
           "ipip"
           "ipvlan"
+          "ipvtap"
           "macvlan"
           "macvtap"
           "sit"
@@ -159,6 +170,7 @@ let
           "geneve"
           "l2tp"
           "macsec"
+          "wlan"
           "vrf"
           "vcan"
           "vxcan"
@@ -190,6 +202,10 @@ let
         (assertValueOneOf "ReorderHeader" boolValues)
       ];
 
+      sectionIPVLAN = checkUnitConfig "IPVLAN" ipVlanVtapChecks;
+
+      sectionIPVTAP = checkUnitConfig "IPVTAP" ipVlanVtapChecks;
+
       sectionMACVLAN = checkUnitConfig "MACVLAN" [
         (assertOnlyFields [
           "Mode"
@@ -468,6 +484,30 @@ let
         (assertMinimum "Table" 0)
       ];
 
+      sectionWLAN = checkUnitConfig "WLAN" [
+        (assertOnlyFields [
+          "PhysicalDevice"  # systemd supports both strings ("phy0") and indexes (0) here.
+          "Type"
+          "WDS"
+        ])
+        # See https://github.com/systemd/systemd/blob/main/src/basic/linux/nl80211.h#L3382
+        (assertValueOneOf "Type" [
+          "ad-hoc"
+          "station"
+          "ap"
+          "ap-vlan"
+          "wds"
+          "monitor"
+          "mesh-point"
+          "p2p-client"
+          "p2p-go"
+          "p2p-device"
+          "ocb"
+          "nan"
+        ])
+        (assertValueOneOf "WDS" boolValues)
+      ];
+
       sectionBatmanAdvanced = checkUnitConfig "BatmanAdvanced" [
         (assertOnlyFields [
           "GatewayMode"
@@ -517,17 +557,24 @@ let
         (assertValueOneOf "Unmanaged" boolValues)
         (assertInt "Group")
         (assertRange "Group" 0 2147483647)
-        (assertValueOneOf "RequiredForOnline" (boolValues ++ [
-          "missing"
-          "off"
-          "no-carrier"
-          "dormant"
-          "degraded-carrier"
-          "carrier"
-          "degraded"
-          "enslaved"
-          "routable"
-        ]))
+        (assertValueOneOf "RequiredForOnline" (boolValues ++ (
+          let
+            # https://freedesktop.org/software/systemd/man/networkctl.html#missing
+            operationalStates = [
+              "missing"
+              "off"
+              "no-carrier"
+              "dormant"
+              "degraded-carrier"
+              "carrier"
+              "degraded"
+              "enslaved"
+              "routable"
+            ];
+            operationalStateRanges = concatLists (imap0 (i: min: map (max: "${min}:${max}") (drop i operationalStates)) operationalStates);
+          in
+          operationalStates ++ operationalStateRanges
+        )))
         (assertValueOneOf "RequiredFamilyForOnline" [
           "ipv4"
           "ipv6"
@@ -583,6 +630,7 @@ let
           "VRF"
           "VLAN"
           "IPVLAN"
+          "IPVTAP"
           "MACVLAN"
           "MACVTAP"
           "VXLAN"
@@ -799,6 +847,8 @@ let
           "UseAddress"
           "UseDNS"
           "UseNTP"
+          "UseHostname"
+          "UseDomains"
           "RouteMetric"
           "RapidCommit"
           "MUDURL"
@@ -813,16 +863,20 @@ let
           "DUIDRawData"
           "IAID"
           "UseDelegatedPrefix"
+          "SendRelease"
         ])
         (assertValueOneOf "UseAddress" boolValues)
         (assertValueOneOf "UseDNS" boolValues)
         (assertValueOneOf "UseNTP" boolValues)
+        (assertValueOneOf "UseHostname" boolValues)
+        (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
         (assertInt "RouteMetric")
         (assertValueOneOf "RapidCommit" boolValues)
         (assertValueOneOf "WithoutRA" ["no" "solicit" "information-request"])
         (assertRange "SendOption" 1 65536)
         (assertInt "IAID")
         (assertValueOneOf "UseDelegatedPrefix" boolValues)
+        (assertValueOneOf "SendRelease" boolValues)
       ];
 
       sectionDHCPPrefixDelegation = checkUnitConfig "DHCPPrefixDelegation" [
@@ -948,10 +1002,12 @@ let
           "Prefix"
           "PreferredLifetimeSec"
           "ValidLifetimeSec"
+          "Assign"
           "Token"
         ])
         (assertValueOneOf "AddressAutoconfiguration" boolValues)
         (assertValueOneOf "OnLink" boolValues)
+        (assertValueOneOf "Assign" boolValues)
       ];
 
       sectionIPv6RoutePrefix = checkUnitConfig "IPv6RoutePrefix" [
@@ -980,7 +1036,7 @@ let
           "MulticastToUnicast"
           "NeighborSuppression"
           "Learning"
-          "Hairpin"
+          "HairPin"
           "Isolated"
           "UseBPDU"
           "FastLeave"
@@ -996,7 +1052,7 @@ let
         (assertValueOneOf "MulticastToUnicast" boolValues)
         (assertValueOneOf "NeighborSuppression" boolValues)
         (assertValueOneOf "Learning" boolValues)
-        (assertValueOneOf "Hairpin" boolValues)
+        (assertValueOneOf "HairPin" boolValues)
         (assertValueOneOf "Isolated" boolValues)
         (assertValueOneOf "UseBPDU" boolValues)
         (assertValueOneOf "FastLeave" boolValues)
@@ -1237,6 +1293,7 @@ let
           "FirewallMark"
           "Wash"
           "SplitGSO"
+          "AckFilter"
         ])
         (assertValueOneOf "AutoRateIngress" boolValues)
         (assertInt "OverheadBytes")
@@ -1269,6 +1326,7 @@ let
         (assertRange "FirewallMark" 1 4294967295)
         (assertValueOneOf "Wash" boolValues)
         (assertValueOneOf "SplitGSO" boolValues)
+        (assertValueOneOf "AckFilter" (boolValues ++ ["aggressive"]))
       ];
 
       sectionControlledDelay = checkUnitConfig "ControlledDelay" [
@@ -1583,6 +1641,26 @@ let
       '';
     };
 
+    ipvlanConfig = mkOption {
+      default = {};
+      example = { Mode = "L2"; Flags = "private"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionIPVLAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the `[IPVLAN]` section of the unit.
+        See {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
+    ipvtapConfig = mkOption {
+      default = {};
+      example = { Mode = "L3"; Flags = "vepa"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionIPVTAP;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the `[IPVTAP]` section of the unit.
+        See {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
     macvlanConfig = mkOption {
       default = {};
       example = { Mode = "private"; };
@@ -1764,6 +1842,16 @@ let
       '';
     };
 
+    wlanConfig = mkOption {
+      default = {};
+      example = { PhysicalDevice = 0; Type = "station"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWLAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the `[WLAN]` section of the unit.
+        See {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
     batmanAdvancedConfig = mkOption {
       default = {};
       example = {
@@ -2709,9 +2797,12 @@ let
         description = lib.mdDoc ''
           Whether to consider the network online when any interface is online, as opposed to all of them.
           This is useful on portable machines with a wired and a wireless interface, for example.
+
+          This is on by default if {option}`networking.useDHCP` is enabled.
         '';
         type = types.bool;
-        default = false;
+        defaultText = "config.networking.useDHCP";
+        default = config.networking.useDHCP;
       };
 
       ignoredInterfaces = mkOption {
@@ -2843,6 +2934,17 @@ let
     })
   ];
 
+  stage1Options = {
+    options.boot.initrd.systemd.network.networks = mkOption {
+      type = with types; attrsOf (submodule {
+        # Default in initrd is dhcp-on-stop, which is correct if flushBeforeStage2 = false
+        config = mkIf config.boot.initrd.network.flushBeforeStage2 {
+          networkConfig.KeepConfiguration = mkDefault false;
+        };
+      });
+    };
+  };
+
   stage1Config = let
     cfg = config.boot.initrd.systemd.network;
   in mkMerge [
@@ -2861,8 +2963,6 @@ let
 
     (mkIf cfg.enable {
 
-      systemd.package = mkDefault pkgs.systemdStage1Network;
-
       # For networkctl
       systemd.dbus.enable = mkDefault true;
 
@@ -2906,45 +3006,14 @@ let
       ];
       kernelModules = [ "af_packet" ];
 
-      systemd.services.nixos-flush-networkd = mkIf config.boot.initrd.network.flushBeforeStage2 {
-        description = "Flush Network Configuration";
-        wantedBy = ["initrd.target"];
-        after = ["systemd-networkd.service" "dbus.socket" "dbus.service"];
-        before = ["shutdown.target" "initrd-switch-root.target"];
-        conflicts = ["shutdown.target" "initrd-switch-root.target"];
-        unitConfig.DefaultDependencies = false;
-        serviceConfig = {
-          # This service does nothing when starting, but brings down
-          # interfaces when switching root. This is the easiest way to
-          # ensure proper ordering while stopping. See systemd.unit(5)
-          # section on Before= and After=. The important part is that
-          # we are stopped before units we need, like dbus.service,
-          # and that we are stopped before starting units like
-          # initrd-switch-root.target
-          Type = "oneshot";
-          RemainAfterExit = true;
-          ExecStart = "/bin/true";
-        };
-        # systemd-networkd doesn't bring down interfaces on its own
-        # when it exits (see: systemd-networkd(8)), so we have to do
-        # it ourselves. The networkctl command doesn't have a way to
-        # bring all interfaces down, so we have to iterate over the
-        # list and filter out unmanaged interfaces to bring them down
-        # individually.
-        preStop = ''
-          networkctl list --full --no-legend | while read _idx link _type _operational setup _; do
-            [ "$setup" = unmanaged ] && continue
-            networkctl down "$link"
-          done
-        '';
-      };
-
     })
   ];
 
 in
 
 {
+  imports = [ stage1Options ];
+
   options = {
     systemd.network = commonOptions true;
     boot.initrd.systemd.network = commonOptions "shallow";
@@ -2954,10 +3023,10 @@ in
     stage2Config
     (mkIf config.boot.initrd.systemd.enable {
       assertions = [{
-        assertion = config.boot.initrd.network.udhcpc.extraArgs == [];
+        assertion = !config.boot.initrd.network.udhcpc.enable && config.boot.initrd.network.udhcpc.extraArgs == [];
         message = ''
-          boot.initrd.network.udhcpc.extraArgs is not supported when
-          boot.initrd.systemd.enable is enabled
+          systemd stage 1 networking does not support 'boot.initrd.network.udhcpc'. Configure
+          DHCP with 'networking.*' options or with 'boot.initrd.systemd.network' options.
         '';
       }];
 
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index bc2fc7f7b10..086e5d65da2 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -253,9 +253,6 @@ done
 @setHostId@
 
 # Load the required kernel modules.
-mkdir -p /lib
-ln -s @modulesClosure@/lib/modules /lib/modules
-ln -s @modulesClosure@/lib/firmware /lib/firmware
 echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
 for i in @kernelModules@; do
     info "loading module $(basename $i)..."
@@ -498,6 +495,8 @@ if test -e /sys/power/resume -a -e /sys/power/disk; then
     fi
 fi
 
+@postResumeCommands@
+
 # If we have a path to an iso file, find the iso and link it to /dev/root
 if [ -n "$isoPath" ]; then
   mkdir -p /findiso
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index a3551f68dbe..13c71e1495e 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -284,7 +284,7 @@ let
       # in the NixOS installation CD, so use ID_CDROM_MEDIA in the
       # corresponding udev rules for now.  This was the behaviour in
       # udev <= 154.  See also
-      #   http://www.spinics.net/lists/hotplug/msg03935.html
+      #   https://www.spinics.net/lists/hotplug/msg03935.html
       substituteInPlace $out/60-persistent-storage.rules \
         --replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
     ''; # */
@@ -307,7 +307,7 @@ let
       ${pkgs.buildPackages.busybox}/bin/ash -n $target
     '';
 
-    inherit linkUnits udevRules extraUtils modulesClosure;
+    inherit linkUnits udevRules extraUtils;
 
     inherit (config.boot) resumeDevice;
 
@@ -316,7 +316,7 @@ let
     inherit (config.system.build) earlyMountScript;
 
     inherit (config.boot.initrd) checkJournalingFS verbose
-      preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
+      preLVMCommands preDeviceCommands postDeviceCommands postResumeCommands postMountCommands preFailCommands kernelModules;
 
     resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}")
                     (filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable
@@ -349,6 +349,9 @@ let
       [ { object = bootStage1;
           symlink = "/init";
         }
+        { object = "${modulesClosure}/lib";
+          symlink = "/lib";
+        }
         { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" {
               src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
               preferLocalBuild = true;
@@ -524,6 +527,14 @@ in
       '';
     };
 
+    boot.initrd.postResumeCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = lib.mdDoc ''
+        Shell commands to be executed immediately after attempting to resume.
+      '';
+    };
+
     boot.initrd.postMountCommands = mkOption {
       default = "";
       type = types.lines;
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index 5a2133f960e..a89e3d81763 100755
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -54,7 +54,7 @@ if [ ! -e /proc/1 ]; then
 fi
 
 
-if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ]; then
+if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ] || [ ! -c /dev/kmsg ] ; then
     echo "booting system configuration ${systemConfig}"
 else
     echo "booting system configuration $systemConfig" > /dev/kmsg
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index b6c3085c4f1..68a8c1f37ed 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -48,6 +48,7 @@ let
       "rescue.service"
 
       # Udev.
+      "systemd-tmpfiles-setup-dev-early.service"
       "systemd-udevd-control.socket"
       "systemd-udevd-kernel.socket"
       "systemd-udevd.service"
@@ -574,7 +575,7 @@ in
     system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
       [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
         "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC"
-        "CRYPTO_SHA256" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL"
+        "CRYPTO_SHA256" "DMIID" "AUTOFS_FS" "TMPFS_POSIX_ACL"
         "TMPFS_XATTR" "SECCOMP"
       ];
 
diff --git a/nixos/modules/system/boot/systemd/homed.nix b/nixos/modules/system/boot/systemd/homed.nix
index 403d1690124..b216820c0c0 100644
--- a/nixos/modules/system/boot/systemd/homed.nix
+++ b/nixos/modules/system/boot/systemd/homed.nix
@@ -5,7 +5,7 @@ let
 in
 {
   options.services.homed.enable = lib.mkEnableOption (lib.mdDoc ''
-    Enable systemd home area/user account manager
+    systemd home area/user account manager
   '');
 
   config = lib.mkIf cfg.enable {
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index 5d9fca7a605..0e7d59b3207 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -57,7 +57,6 @@ let
     "systemd-ask-password-console.service"
     "systemd-fsck@.service"
     "systemd-halt.service"
-    "systemd-hibernate-resume@.service"
     "systemd-journald-audit.socket"
     "systemd-journald-dev-log.socket"
     "systemd-journald.service"
@@ -129,15 +128,16 @@ in {
         stage 2 counterparts such as {option}`systemd.services`,
         except that `restartTriggers` and `reloadTriggers` are not
         supported.
-
-        Note: This is experimental. Some of the `boot.initrd` options
-        are not supported when this is enabled, and the options under
-        `boot.initrd.systemd` are subject to change.
       '';
     };
 
-    package = mkPackageOptionMD pkgs "systemd" {
-      default = "systemdStage1";
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = config.systemd.package;
+      defaultText = lib.literalExpression "config.systemd.package";
+      description = ''
+        The systemd package to use.
+      '';
     };
 
     extraConfig = mkOption {
@@ -344,17 +344,39 @@ in {
   };
 
   config = mkIf (config.boot.initrd.enable && cfg.enable) {
+    assertions = map (name: {
+      assertion = lib.attrByPath name (throw "impossible") config.boot.initrd == "";
+      message = ''
+        systemd stage 1 does not support 'boot.initrd.${lib.concatStringsSep "." name}'. Please
+          convert it to analogous systemd units in 'boot.initrd.systemd'.
+
+            Definitions:
+        ${lib.concatMapStringsSep "\n" ({ file, ... }: "    - ${file}") (lib.attrByPath name (throw "impossible") options.boot.initrd).definitionsWithLocations}
+      '';
+    }) [
+      [ "preFailCommands" ]
+      [ "preDeviceCommands" ]
+      [ "preLVMCommands" ]
+      [ "postDeviceCommands" ]
+      [ "postResumeCommands" ]
+      [ "postMountCommands" ]
+      [ "extraUdevRulesCommands" ]
+      [ "extraUtilsCommands" ]
+      [ "extraUtilsCommandsTest" ]
+      [ "network" "postCommands" ]
+    ];
+
     system.build = { inherit initialRamdisk; };
 
     boot.initrd.availableKernelModules = [
       # systemd needs this for some features
-      "autofs4"
+      "autofs"
       # systemd-cryptenroll
     ] ++ lib.optional cfg.enableTpm2 "tpm-tis"
     ++ lib.optional (cfg.enableTpm2 && !(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7)) "tpm-crb";
 
     boot.initrd.systemd = {
-      initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages;
+      initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package];
       extraBin = {
         less = "${pkgs.less}/bin/less";
         mount = "${cfg.package.util-linux}/bin/mount";
diff --git a/nixos/modules/system/boot/systemd/journald.nix b/nixos/modules/system/boot/systemd/journald.nix
index 773163bbcb8..7e62a4c9bfe 100644
--- a/nixos/modules/system/boot/systemd/journald.nix
+++ b/nixos/modules/system/boot/systemd/journald.nix
@@ -28,6 +28,15 @@ in {
       '';
     };
 
+    services.journald.storage = mkOption {
+      default = "persistent";
+      type = types.enum [ "persistent" "volatile" "auto" "none" ];
+      description = mdDoc ''
+        Controls where to store journal data. See
+        {manpage}`journald.conf(5)` for further information.
+      '';
+    };
+
     services.journald.rateLimitBurst = mkOption {
       default = 10000;
       type = types.int;
@@ -100,7 +109,7 @@ in {
     environment.etc = {
       "systemd/journald.conf".text = ''
         [Journal]
-        Storage=persistent
+        Storage=${cfg.storage}
         RateLimitInterval=${cfg.rateLimitInterval}
         RateLimitBurst=${toString cfg.rateLimitBurst}
         ${optionalString (cfg.console != "") ''
diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix
index 2431c68ea17..5ac2ace56ba 100644
--- a/nixos/modules/system/boot/systemd/repart.nix
+++ b/nixos/modules/system/boot/systemd/repart.nix
@@ -74,6 +74,15 @@ in
   };
 
   config = lib.mkIf (cfg.enable || initrdCfg.enable) {
+    assertions = [
+      {
+        assertion = initrdCfg.enable -> config.boot.initrd.systemd.enable;
+        message = ''
+          'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled.
+        '';
+      }
+    ];
+
     boot.initrd.systemd = lib.mkIf initrdCfg.enable {
       additionalUpstreamUnits = [
         "systemd-repart.service"
diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix
index 32b9b275d35..183e2033ecb 100644
--- a/nixos/modules/system/boot/systemd/tmpfiles.nix
+++ b/nixos/modules/system/boot/systemd/tmpfiles.nix
@@ -20,6 +20,102 @@ in
       '';
     };
 
+    systemd.tmpfiles.settings = mkOption {
+      description = lib.mdDoc ''
+        Declare systemd-tmpfiles rules to create, delete, and clean up volatile
+        and temporary files and directories.
+
+        Even though the service is called `*tmp*files` you can also create
+        persistent files.
+      '';
+      example = {
+        "10-mypackage" = {
+          "/var/lib/my-service/statefolder".d = {
+            mode = "0755";
+            user = "root";
+            group = "root";
+          };
+        };
+      };
+      default = {};
+      type = types.attrsOf (types.attrsOf (types.attrsOf (types.submodule ({ name, config, ... }: {
+        options.type = mkOption {
+          type = types.str;
+          default = name;
+          example = "d";
+          description = lib.mdDoc ''
+            The type of operation to perform on the file.
+
+            The type consists of a single letter and optionally one or more
+            modifier characters.
+
+            Please see the upstream documentation for the available types and
+            more details:
+            <https://www.freedesktop.org/software/systemd/man/tmpfiles.d>
+          '';
+        };
+        options.mode = mkOption {
+          type = types.str;
+          default = "-";
+          example = "0755";
+          description = lib.mdDoc ''
+            The file access mode to use when creating this file or directory.
+          '';
+        };
+        options.user = mkOption {
+          type = types.str;
+          default = "-";
+          example = "root";
+          description = lib.mdDoc ''
+            The user of the file.
+
+            This may either be a numeric ID or a user/group name.
+
+            If omitted or when set to `"-"`, the user and group of the user who
+            invokes systemd-tmpfiles is used.
+          '';
+        };
+        options.group = mkOption {
+          type = types.str;
+          default = "-";
+          example = "root";
+          description = lib.mdDoc ''
+            The group of the file.
+
+            This may either be a numeric ID or a user/group name.
+
+            If omitted or when set to `"-"`, the user and group of the user who
+            invokes systemd-tmpfiles is used.
+          '';
+        };
+        options.age = mkOption {
+          type = types.str;
+          default = "-";
+          example = "10d";
+          description = lib.mdDoc ''
+            Delete a file when it reaches a certain age.
+
+            If a file or directory is older than the current time minus the age
+            field, it is deleted.
+
+            If set to `"-"` no automatic clean-up is done.
+          '';
+        };
+        options.argument = mkOption {
+          type = types.str;
+          default = "";
+          example = "";
+          description = lib.mdDoc ''
+            An argument whose meaning depends on the type of operation.
+
+            Please see the upstream documentation for the meaning of this
+            parameter in different situations:
+            <https://www.freedesktop.org/software/systemd/man/tmpfiles.d>
+          '';
+        };
+      }))));
+    };
+
     systemd.tmpfiles.packages = mkOption {
       type = types.listOf types.package;
       default = [];
@@ -100,7 +196,13 @@ in
           ${concatStringsSep "\n" cfg.rules}
         '';
       })
-    ];
+    ] ++ (mapAttrsToList (name: paths:
+      pkgs.writeTextDir "lib/tmpfiles.d/${name}.conf" (concatStrings (mapAttrsToList (path: types:
+        concatStrings (mapAttrsToList (_type: entry: ''
+          '${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${entry.argument}
+        '') types)
+      ) paths ))
+    ) cfg.settings);
 
     systemd.tmpfiles.rules = [
       "d  /nix/var                           0755 root root - -"
diff --git a/nixos/modules/system/boot/systemd/userdbd.nix b/nixos/modules/system/boot/systemd/userdbd.nix
index 994aa3ca3b8..e7f6d42341c 100644
--- a/nixos/modules/system/boot/systemd/userdbd.nix
+++ b/nixos/modules/system/boot/systemd/userdbd.nix
@@ -5,7 +5,7 @@ let
 in
 {
   options.services.userdbd.enable = lib.mkEnableOption (lib.mdDoc ''
-    Enables the systemd JSON user/group record lookup service
+    the systemd JSON user/group record lookup service
   '');
   config = lib.mkIf cfg.enable {
     systemd.additionalUpstreamSystemUnits = [
diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix
index a6604802c38..7487cf97fe5 100644
--- a/nixos/modules/system/boot/timesyncd.nix
+++ b/nixos/modules/system/boot/timesyncd.nix
@@ -46,6 +46,28 @@ with lib;
       wantedBy = [ "sysinit.target" ];
       aliases = [ "dbus-org.freedesktop.timesync1.service" ];
       restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ];
+
+      preStart = (
+        # Ensure that we have some stored time to prevent
+        # systemd-timesyncd to resort back to the fallback time.  If
+        # the file doesn't exist we assume that our current system
+        # clock is good enough to provide an initial value.
+        ''
+          if ! [ -f /var/lib/systemd/timesync/clock ]; then
+            test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync
+            touch /var/lib/systemd/timesync/clock
+          fi
+        '' +
+        # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
+        #  - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
+        #  - https://github.com/systemd/systemd/issues/12131
+        (lib.optionalString (versionOlder config.system.stateVersion "19.09") ''
+          if [ -L /var/lib/systemd/timesync ]; then
+            rm /var/lib/systemd/timesync
+            mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
+          fi
+        '')
+      );
     };
 
     environment.etc."systemd/timesyncd.conf".text = ''
@@ -59,28 +81,5 @@ with lib;
       group = "systemd-timesync";
     };
     users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
-
-    system.activationScripts.systemd-timesyncd-migration =
-      # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
-      #  - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
-      #  - https://github.com/systemd/systemd/issues/12131
-      mkIf (versionOlder config.system.stateVersion "19.09") ''
-        if [ -L /var/lib/systemd/timesync ]; then
-          rm /var/lib/systemd/timesync
-          mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
-        fi
-      '';
-    system.activationScripts.systemd-timesyncd-init-clock =
-      # Ensure that we have some stored time to prevent systemd-timesyncd to
-      # resort back to the fallback time.
-      # If the file doesn't exist we assume that our current system clock is
-      # good enough to provide an initial value.
-      ''
-      if ! [ -f /var/lib/systemd/timesync/clock ]; then
-        test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync
-        touch /var/lib/systemd/timesync/clock
-      fi
-      '';
   };
-
 }
diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix
index 7837a34b498..da9c83ba339 100644
--- a/nixos/modules/tasks/encrypted-devices.nix
+++ b/nixos/modules/tasks/encrypted-devices.nix
@@ -5,8 +5,22 @@ with lib;
 let
   fileSystems = config.system.build.fileSystems ++ config.swapDevices;
   encDevs = filter (dev: dev.encrypted.enable) fileSystems;
-  keyedEncDevs = filter (dev: dev.encrypted.keyFile != null) encDevs;
-  keylessEncDevs = filter (dev: dev.encrypted.keyFile == null) encDevs;
+
+  # With scripted initrd, devices with a keyFile have to be opened
+  # late, after file systems are mounted, because that could be where
+  # the keyFile is located. With systemd initrd, each individual
+  # systemd-cryptsetup@ unit has RequiresMountsFor= to delay until all
+  # the mount units for the key file are done; i.e. no special
+  # treatment is needed.
+  lateEncDevs =
+    if config.boot.initrd.systemd.enable
+    then { }
+    else filter (dev: dev.encrypted.keyFile != null) encDevs;
+  earlyEncDevs =
+    if config.boot.initrd.systemd.enable
+    then encDevs
+    else filter (dev: dev.encrypted.keyFile == null) encDevs;
+
   anyEncrypted =
     foldr (j: v: v || j.encrypted.enable) false encDevs;
 
@@ -39,11 +53,14 @@ let
         type = types.nullOr types.str;
         description = lib.mdDoc ''
           Path to a keyfile used to unlock the backing encrypted
-          device. At the time this keyfile is accessed, the
-          `neededForBoot` filesystems (see
-          `fileSystems.<name?>.neededForBoot`)
-          will have been mounted under `/mnt-root`,
-          so the keyfile path should usually start with "/mnt-root/".
+          device. When systemd stage 1 is not enabled, at the time
+          this keyfile is accessed, the `neededForBoot` filesystems
+          (see `utils.fsNeededForBoot`) will have been mounted under
+          `/mnt-root`, so the keyfile path should usually start with
+          "/mnt-root/". When systemd stage 1 is enabled,
+          `fsNeededForBoot` file systems will be mounted as needed
+          under `/sysroot`, and the keyfile will not be accessed until
+          its requisite mounts are done.
         '';
       };
     };
@@ -62,26 +79,42 @@ in
   };
 
   config = mkIf anyEncrypted {
-    assertions = map (dev: {
-      assertion = dev.encrypted.label != null;
-      message = ''
-        The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set
-      '';
-    }) encDevs;
+    assertions = concatMap (dev: [
+      {
+        assertion = dev.encrypted.label != null;
+        message = ''
+          The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set
+        '';
+      }
+      {
+        assertion =
+          config.boot.initrd.systemd.enable -> (
+            dev.encrypted.keyFile == null
+            || !lib.any (x: lib.hasPrefix x dev.encrypted.keyFile) ["/mnt-root" "$targetRoot"]
+          );
+        message = ''
+          Bad use of '/mnt-root' or '$targetRoot` in 'keyFile'.
+
+            When 'boot.initrd.systemd.enable' is enabled, file systems
+            are mounted at '/sysroot' instead of '/mnt-root'.
+        '';
+      }
+    ]) encDevs;
 
     boot.initrd = {
       luks = {
         devices =
           builtins.listToAttrs (map (dev: {
             name = dev.encrypted.label;
-            value = { device = dev.encrypted.blkDev; };
-          }) keylessEncDevs);
+            value = { device = dev.encrypted.blkDev; inherit (dev.encrypted) keyFile; };
+          }) earlyEncDevs);
         forceLuksSupportInInitrd = true;
       };
-      postMountCommands =
-        concatMapStrings (dev:
+      # TODO: systemd stage 1
+      postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
+        (concatMapStrings (dev:
           "cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n"
-        ) keyedEncDevs;
+        ) lateEncDevs);
     };
   };
 }
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 7cb2ca23fa4..91e30aa4c0a 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -187,9 +187,8 @@ let
       skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs;
       # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
       escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
-    in fstabFileSystems: { rootPrefix ? "" }: concatMapStrings (fs:
-      (optionalString (isBindMount fs) (escape rootPrefix))
-      + (if fs.device != null then escape fs.device
+    in fstabFileSystems: { }: concatMapStrings (fs:
+      (if fs.device != null then escape fs.device
          else if fs.label != null then "/dev/disk/by-label/${escape fs.label}"
          else throw "No device specified for mount point ‘${fs.mountPoint}’.")
       + " " + escape fs.mountPoint
@@ -199,9 +198,7 @@ let
       + "\n"
     ) fstabFileSystems;
 
-    initrdFstab = pkgs.writeText "initrd-fstab" (makeFstabEntries (filter utils.fsNeededForBoot fileSystems) {
-      rootPrefix = "/sysroot";
-    });
+    initrdFstab = pkgs.writeText "initrd-fstab" (makeFstabEntries (filter utils.fsNeededForBoot fileSystems) { });
 
 in
 
diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix
index 19ef188ce78..d144ce62dc2 100644
--- a/nixos/modules/tasks/filesystems/bcachefs.nix
+++ b/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -1,28 +1,42 @@
 { config, lib, pkgs, utils, ... }:
 
-with lib;
-
 let
 
-  bootFs = filterAttrs (n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)) config.fileSystems;
-
-  mountCommand = pkgs.runCommand "mount.bcachefs" {} ''
-    mkdir -p $out/bin
-    cat > $out/bin/mount.bcachefs <<EOF
-    #!/bin/sh
-    exec "/bin/bcachefs" mount "\$@"
-    EOF
-    chmod +x $out/bin/mount.bcachefs
-  '';
+  bootFs = lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)) config.fileSystems;
 
   commonFunctions = ''
     prompt() {
         local name="$1"
         printf "enter passphrase for $name: "
     }
+
     tryUnlock() {
         local name="$1"
         local path="$2"
+        local success=false
+        local target
+        local uuid=$(echo -n $path | sed -e 's,UUID=\(.*\),\1,g')
+
+        printf "waiting for device to appear $path"
+        for try in $(seq 10); do
+          if [ -e $path ]; then
+              success=true
+              break
+          else
+              target=$(blkid --uuid $uuid)
+              if [ $? == 0 ]; then
+                 success=true
+                 break
+              fi
+          fi
+          echo -n "."
+          sleep 1
+        done
+        printf "\n"
+        if [ $success == true ]; then
+            path=$target
+        fi
+
         if bcachefs unlock -c $path > /dev/null 2> /dev/null; then    # test for encryption
             prompt $name
             until bcachefs unlock $path 2> /dev/null; do              # repeat until successfully unlocked
@@ -30,55 +44,107 @@ let
                 prompt $name
             done
             printf "unlocking successful.\n"
+        else
+            echo "Cannot unlock device $uuid with path $path" >&2
         fi
     }
   '';
 
-  openCommand = name: fs:
-    let
-      # we need only unlock one device manually, and cannot pass multiple at once
-      # remove this adaptation when bcachefs implements mounting by filesystem uuid
-      # also, implement automatic waiting for the constituent devices when that happens
-      # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
-      firstDevice = head (splitString ":" fs.device);
-    in
-      ''
-        tryUnlock ${name} ${firstDevice}
+  # we need only unlock one device manually, and cannot pass multiple at once
+  # remove this adaptation when bcachefs implements mounting by filesystem uuid
+  # also, implement automatic waiting for the constituent devices when that happens
+  # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
+  firstDevice = fs: lib.head (lib.splitString ":" fs.device);
+
+  openCommand = name: fs: ''
+    tryUnlock ${name} ${firstDevice fs}
+  '';
+
+  mkUnits = prefix: name: fs: let
+    mountUnit = "${utils.escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint))}.mount";
+    device = firstDevice fs;
+    deviceUnit = "${utils.escapeSystemdPath device}.device";
+  in {
+    name = "unlock-bcachefs-${utils.escapeSystemdPath fs.mountPoint}";
+    value = {
+      description = "Unlock bcachefs for ${fs.mountPoint}";
+      requiredBy = [ mountUnit ];
+      before = [ mountUnit ];
+      bindsTo = [ deviceUnit ];
+      after = [ deviceUnit ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecCondition = "${pkgs.bcachefs-tools}/bin/bcachefs unlock -c \"${device}\"";
+        Restart = "on-failure";
+        RestartMode = "direct";
+        # Ideally, this service would lock the key on stop.
+        # As is, RemainAfterExit doesn't accomplish anything.
+        RemainAfterExit = true;
+      };
+      script = ''
+        ${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${pkgs.bcachefs-tools}/bin/bcachefs unlock "${device}"
       '';
+    };
+  };
+
+  assertions = [
+    {
+      assertion = let
+        kernel = config.boot.kernelPackages.kernel;
+      in (
+        kernel.kernelAtLeast "6.7" || (
+          lib.elem (kernel.structuredExtraConfig.BCACHEFS_FS or null) [
+            lib.kernel.module
+            lib.kernel.yes
+            lib.kernel.option.yes
+          ]
+        )
+      );
 
+      message = "Linux 6.7-rc1 at minimum or a custom linux kernel with bcachefs support is required";
+    }
+  ];
 in
 
 {
-  config = mkIf (elem "bcachefs" config.boot.supportedFilesystems) (mkMerge [
+  config = lib.mkIf (lib.elem "bcachefs" config.boot.supportedFilesystems) (lib.mkMerge [
     {
-      # We do not want to include bachefs in the fsPackages for systemd-initrd
-      # because we provide the unwrapped version of mount.bcachefs
-      # through the extraBin option, which will make it available for use.
-      system.fsPackages = lib.optional (!config.boot.initrd.systemd.enable) pkgs.bcachefs-tools;
-      environment.systemPackages = lib.optional (config.boot.initrd.systemd.enable) pkgs.bcachefs-tools;
-
-      # use kernel package with bcachefs support until it's in mainline
-      boot.kernelPackages = pkgs.linuxPackages_testing_bcachefs;
+      inherit assertions;
+      # needed for systemd-remount-fs
+      system.fsPackages = [ pkgs.bcachefs-tools ];
+
+      # FIXME: Replace this with `linuxPackages_testing` after NixOS 23.11 is released
+      # FIXME: Replace this with `linuxPackages_latest` when 6.7 is released, remove this line when the LTS version is at least 6.7
+      boot.kernelPackages = lib.mkDefault (
+        # FIXME: Remove warning after NixOS 23.11 is released
+        lib.warn "Please upgrade to Linux 6.7-rc1 or later: 'linuxPackages_testing_bcachefs' is deprecated. Use 'boot.kernelPackages = pkgs.linuxPackages_testing;' to silence this warning"
+        pkgs.linuxPackages_testing_bcachefs
+      );
+
+      systemd.services = lib.mapAttrs' (mkUnits "") (lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (!utils.fsNeededForBoot fs)) config.fileSystems);
     }
 
-    (mkIf ((elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
+    (lib.mkIf ((lib.elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
+      inherit assertions;
       # chacha20 and poly1305 are required only for decryption attempts
       boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ];
-
       boot.initrd.systemd.extraBin = {
+        # do we need this? boot/systemd.nix:566 & boot/systemd/initrd.nix:357
         "bcachefs" = "${pkgs.bcachefs-tools}/bin/bcachefs";
-        "mount.bcachefs" = "${mountCommand}/bin/mount.bcachefs";
+        "mount.bcachefs" = "${pkgs.bcachefs-tools}/bin/mount.bcachefs";
       };
-
       boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
-        copy_bin_and_libs ${mountCommand}/bin/mount.bcachefs
+        copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/mount.bcachefs
       '';
-      boot.initrd.extraUtilsCommandsTest = ''
+      boot.initrd.extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         $out/bin/bcachefs version
       '';
 
-      boot.initrd.postDeviceCommands = commonFunctions + concatStrings (mapAttrsToList openCommand bootFs);
+      boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + lib.concatStrings (lib.mapAttrsToList openCommand bootFs));
+
+      boot.initrd.systemd.services = lib.mapAttrs' (mkUnits "/sysroot") bootFs;
     })
   ]);
 }
diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix
index 82fdd605871..87fe326c097 100644
--- a/nixos/modules/tasks/filesystems/btrfs.nix
+++ b/nixos/modules/tasks/filesystems/btrfs.nix
@@ -52,34 +52,37 @@ in
   config = mkMerge [
     (mkIf enableBtrfs {
       system.fsPackages = [ pkgs.btrfs-progs ];
+    })
 
-      boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" ];
-      boot.initrd.availableKernelModules = mkIf inInitrd (
+    (mkIf inInitrd {
+      boot.initrd.kernelModules = [ "btrfs" ];
+      boot.initrd.availableKernelModules =
         [ "crc32c" ]
         ++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [
           # Needed for mounting filesystems with new checksums
           "xxhash_generic"
           "blake2b_generic"
           "sha256_generic" # Should be baked into our kernel, just to be sure
-        ]
-      );
+        ];
 
-      boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
+      boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable)
       ''
         copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
         ln -sv btrfs $out/bin/btrfsck
         ln -sv btrfsck $out/bin/fsck.btrfs
       '';
 
-      boot.initrd.extraUtilsCommandsTest = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
+      boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable)
       ''
         $out/bin/btrfs --version
       '';
 
-      boot.initrd.postDeviceCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
+      boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable)
       ''
         btrfs device scan
       '';
+
+      boot.initrd.systemd.initrdBin = [ pkgs.btrfs-progs ];
     })
 
     (mkIf enableAutoScrub {
diff --git a/nixos/modules/tasks/filesystems/cifs.nix b/nixos/modules/tasks/filesystems/cifs.nix
index 0de292a6920..837b9e19bfb 100644
--- a/nixos/modules/tasks/filesystems/cifs.nix
+++ b/nixos/modules/tasks/filesystems/cifs.nix
@@ -21,5 +21,7 @@ in
         copy_bin_and_libs ${pkgs.cifs-utils}/sbin/mount.cifs
       '';
 
+    boot.initrd.systemd.extraBin."mount.cifs" = mkIf inInitrd "${pkgs.cifs-utils}/sbin/mount.cifs";
+
   };
 }
diff --git a/nixos/modules/tasks/filesystems/ext.nix b/nixos/modules/tasks/filesystems/ext.nix
index edc0efc5521..1c34ee2c703 100644
--- a/nixos/modules/tasks/filesystems/ext.nix
+++ b/nixos/modules/tasks/filesystems/ext.nix
@@ -25,5 +25,7 @@ in
         ln -sv e2fsck $out/bin/fsck.ext4
       '';
 
+    boot.initrd.systemd.initrdBin = lib.mkIf inInitrd [ pkgs.e2fsprogs ];
+
   };
 }
diff --git a/nixos/modules/tasks/filesystems/f2fs.nix b/nixos/modules/tasks/filesystems/f2fs.nix
index 035784f43df..4f99f9a57fa 100644
--- a/nixos/modules/tasks/filesystems/f2fs.nix
+++ b/nixos/modules/tasks/filesystems/f2fs.nix
@@ -16,5 +16,7 @@ in
     boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/fsck.f2fs
     '';
+
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.f2fs-tools ];
   };
 }
diff --git a/nixos/modules/tasks/filesystems/jfs.nix b/nixos/modules/tasks/filesystems/jfs.nix
index 6d80c4c657d..b5132b4caa3 100644
--- a/nixos/modules/tasks/filesystems/jfs.nix
+++ b/nixos/modules/tasks/filesystems/jfs.nix
@@ -15,5 +15,7 @@ in
     boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.jfsutils}/sbin/fsck.jfs
     '';
+
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.jfsutils ];
   };
 }
diff --git a/nixos/modules/tasks/filesystems/reiserfs.nix b/nixos/modules/tasks/filesystems/reiserfs.nix
index 7b017a83db8..3c6a0f0cd91 100644
--- a/nixos/modules/tasks/filesystems/reiserfs.nix
+++ b/nixos/modules/tasks/filesystems/reiserfs.nix
@@ -21,5 +21,7 @@ in
         ln -s reiserfsck $out/bin/fsck.reiserfs
       '';
 
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.reiserfsprogs ];
+
   };
 }
diff --git a/nixos/modules/tasks/filesystems/vfat.nix b/nixos/modules/tasks/filesystems/vfat.nix
index 5421b617b43..9281b34633c 100644
--- a/nixos/modules/tasks/filesystems/vfat.nix
+++ b/nixos/modules/tasks/filesystems/vfat.nix
@@ -21,5 +21,7 @@ in
         ln -sv dosfsck $out/bin/fsck.vfat
       '';
 
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.dosfstools ];
+
   };
 }
diff --git a/nixos/modules/tasks/filesystems/xfs.nix b/nixos/modules/tasks/filesystems/xfs.nix
index f81f5864655..76f31e660ad 100644
--- a/nixos/modules/tasks/filesystems/xfs.nix
+++ b/nixos/modules/tasks/filesystems/xfs.nix
@@ -26,5 +26,7 @@ in
       ''
         sed -i -e 's,^#!.*,#!'$out/bin/sh, $out/bin/fsck.xfs
       '';
+
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.xfsprogs.bin ];
   };
 }
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 5cf863c87f2..4b6a5b6c12c 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -90,12 +90,17 @@ let
 
   getPoolMounts = prefix: pool:
     let
+      poolFSes = getPoolFilesystems pool;
+
       # Remove the "/" suffix because even though most mountpoints
       # won't have it, the "/" mountpoint will, and we can't have the
       # trailing slash in "/sysroot/" in stage 1.
       mountPoint = fs: escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint));
+
+      hasUsr = lib.any (fs: fs.mountPoint == "/usr") poolFSes;
     in
-      map (x: "${mountPoint x}.mount") (getPoolFilesystems pool);
+      map (x: "${mountPoint x}.mount") poolFSes
+      ++ lib.optional hasUsr "sysusr-usr.mount";
 
   getKeyLocations = pool: if isBool cfgZfs.requestEncryptionCredentials then {
     hasKeys = cfgZfs.requestEncryptionCredentials;
@@ -579,17 +584,17 @@ in
       boot.initrd = mkIf inInitrd {
         kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl";
         extraUtilsCommands =
-          ''
+          mkIf (!config.boot.initrd.systemd.enable) ''
             copy_bin_and_libs ${cfgZfs.package}/sbin/zfs
             copy_bin_and_libs ${cfgZfs.package}/sbin/zdb
             copy_bin_and_libs ${cfgZfs.package}/sbin/zpool
           '';
-        extraUtilsCommandsTest = mkIf inInitrd
-          ''
+        extraUtilsCommandsTest =
+          mkIf (!config.boot.initrd.systemd.enable) ''
             $out/bin/zfs --help >/dev/null 2>&1
             $out/bin/zpool --help >/dev/null 2>&1
           '';
-        postDeviceCommands = concatStringsSep "\n" ([''
+        postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (concatStringsSep "\n" ([''
             ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}"
           ''] ++ [(importLib {
             # See comments at importLib definition.
@@ -618,10 +623,10 @@ in
               else concatMapStrings (fs: ''
                 zfs load-key -- ${escapeShellArg fs}
               '') (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)}
-        '') rootPools));
+        '') rootPools)));
 
         # Systemd in stage 1
-        systemd = {
+        systemd = mkIf config.boot.initrd.systemd.enable {
           packages = [cfgZfs.package];
           services = listToAttrs (map (pool: createImportService {
             inherit pool;
@@ -632,7 +637,8 @@ in
           targets.zfs-import.wantedBy = [ "zfs.target" ];
           targets.zfs.wantedBy = [ "initrd.target" ];
           extraBin = {
-            # zpool and zfs are already in thanks to fsPackages
+            zpool = "${cfgZfs.package}/sbin/zpool";
+            zfs = "${cfgZfs.package}/sbin/zfs";
             awk = "${pkgs.gawk}/bin/awk";
           };
         };
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index 24f0c37acf9..e1ac7f24cb3 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -28,12 +28,12 @@ let
       SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
       for I in $SLAVES; do
         UPDATED=0
-        ip link set "$I" nomaster
+        ip link set dev "$I" nomaster
       done
       [ "$UPDATED" -eq "1" ] && break
     done
-    ip link set "${i}" down 2>/dev/null || true
-    ip link del "${i}" 2>/dev/null || true
+    ip link set dev "${i}" down 2>/dev/null || true
+    ip link del dev "${i}" 2>/dev/null || true
   '';
 
   # warn that these attributes are deprecated (2017-2-2)
@@ -61,8 +61,6 @@ let
           MACAddress = i.macAddress;
         } // optionalAttrs (i.mtu != null) {
           MTUBytes = toString i.mtu;
-        } // optionalAttrs (i.wakeOnLan.enable == true) {
-          WakeOnLan = "magic";
         };
       };
     in listToAttrs (map createNetworkLink interfaces);
@@ -195,7 +193,7 @@ let
                 state="/run/nixos/network/addresses/${i.name}"
                 mkdir -p $(dirname "$state")
 
-                ip link set "${i.name}" up
+                ip link set dev "${i.name}" up
 
                 ${flip concatMapStrings ips (ip:
                   let
@@ -272,7 +270,7 @@ let
               ip tuntap add dev "${i.name}" mode "${i.virtualType}" user "${i.virtualOwner}"
             '';
             postStop = ''
-              ip link del ${i.name} || true
+              ip link del dev ${i.name} || true
             '';
           };
 
@@ -293,15 +291,15 @@ let
             script = ''
               # Remove Dead Interfaces
               echo "Removing old bridge ${n}..."
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link del "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link del dev "${n}"
 
               echo "Adding bridge ${n}..."
               ip link add name "${n}" type bridge
 
               # Enslave child interfaces
               ${flip concatMapStrings v.interfaces (i: ''
-                ip link set "${i}" master "${n}"
-                ip link set "${i}" up
+                ip link set dev "${i}" master "${n}"
+                ip link set dev "${i}" up
               '')}
               # Save list of enslaved interfaces
               echo "${flip concatMapStrings v.interfaces (i: ''
@@ -318,7 +316,7 @@ let
                     for uri in qemu:///system lxc:///; do
                       for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do
                         ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \
-                        ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set ',target/@dev,' master ',source/@bridge,';')" | \
+                        ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set dev ',target/@dev,' master ',source/@bridge,';')" | \
                         ${pkgs.bash}/bin/bash
                       done
                     done
@@ -330,23 +328,23 @@ let
                 echo 2 >/sys/class/net/${n}/bridge/stp_state
               ''}
 
-              ip link set "${n}" up
+              ip link set dev "${n}" up
             '';
             postStop = ''
-              ip link set "${n}" down || true
-              ip link del "${n}" || true
+              ip link set dev "${n}" down || true
+              ip link del dev "${n}" || true
               rm -f /run/${n}.interfaces
             '';
             reload = ''
               # Un-enslave child interfaces (old list of interfaces)
               for interface in `cat /run/${n}.interfaces`; do
-                ip link set "$interface" nomaster up
+                ip link set dev "$interface" nomaster up
               done
 
               # Enslave child interfaces (new list of interfaces)
               ${flip concatMapStrings v.interfaces (i: ''
-                ip link set "${i}" master "${n}"
-                ip link set "${i}" up
+                ip link set dev "${i}" master "${n}"
+                ip link set dev "${i}" up
               '')}
               # Save list of enslaved interfaces
               echo "${flip concatMapStrings v.interfaces (i: ''
@@ -397,7 +395,7 @@ let
             postStop = ''
               echo "Cleaning Open vSwitch ${n}"
               echo "Shutting down internal ${n} interface"
-              ip link set ${n} down || true
+              ip link set dev ${n} down || true
               echo "Deleting flows for ${n}"
               ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
               echo "Deleting Open vSwitch ${n}"
@@ -435,10 +433,10 @@ let
               while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
 
               # Bring up the bond and enslave the specified interfaces
-              ip link set "${n}" up
+              ip link set dev "${n}" up
               ${flip concatMapStrings v.interfaces (i: ''
-                ip link set "${i}" down
-                ip link set "${i}" master "${n}"
+                ip link set dev "${i}" down
+                ip link set dev "${i}" master "${n}"
               '')}
             '';
             postStop = destroyBond n;
@@ -459,13 +457,13 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
               ip link add link "${v.interface}" name "${n}" type macvlan \
                 ${optionalString (v.mode != null) "mode ${v.mode}"}
-              ip link set "${n}" up
+              ip link set dev "${n}" up
             '';
             postStop = ''
-              ip link delete "${n}" || true
+              ip link delete dev "${n}" || true
             '';
           });
 
@@ -517,7 +515,7 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
               ip link add name "${n}" type sit \
                 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
                 ${optionalString (v.local != null) "local \"${v.local}\""} \
@@ -528,10 +526,10 @@ let
                     optionalString (v.encapsulation.sourcePort != null)
                       "encap-sport ${toString v.encapsulation.sourcePort}"
                   }"}
-              ip link set "${n}" up
+              ip link set dev "${n}" up
             '';
             postStop = ''
-              ip link delete "${n}" || true
+              ip link delete dev "${n}" || true
             '';
           });
 
@@ -551,16 +549,16 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
               ip link add name "${n}" type ${v.type} \
                 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
                 ${optionalString (v.local != null) "local \"${v.local}\""} \
                 ${optionalString (v.ttl != null) "${ttlarg} ${toString v.ttl}"} \
                 ${optionalString (v.dev != null) "dev \"${v.dev}\""}
-              ip link set "${n}" up
+              ip link set dev "${n}" up
             '';
             postStop = ''
-              ip link delete "${n}" || true
+              ip link delete dev "${n}" || true
             '';
           });
 
@@ -579,17 +577,17 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
               ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
 
               # We try to bring up the logical VLAN interface. If the master
               # interface the logical interface is dependent upon is not up yet we will
               # fail to immediately bring up the logical interface. The resulting logical
               # interface will brought up later when the master interface is up.
-              ip link set "${n}" up || true
+              ip link set dev "${n}" up || true
             '';
             postStop = ''
-              ip link delete "${n}" || true
+              ip link delete dev "${n}" || true
             '';
           });
 
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index 679567cbb73..2009c9a7e6e 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -28,21 +28,20 @@ let
     # TODO: warn the user that any address configured on those interfaces will be useless
     ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches);
 
-  domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
-  genericNetwork = override:
-    let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
-      ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
-        makeGateway = gateway: {
+  defaultGateways = mkMerge (forEach [ cfg.defaultGateway cfg.defaultGateway6 ] (gateway:
+    optionalAttrs (gateway != null && gateway.interface != null) {
+      networks."40-${gateway.interface}" = {
+        matchConfig.Name = gateway.interface;
+        routes = [{
           routeConfig = {
-            Gateway = gateway;
-            GatewayOnLink = false;
+            Gateway = gateway.address;
+          } // optionalAttrs (gateway.metric != null) {
+            Metric = gateway.metric;
           };
-        };
-    in optionalAttrs (gateway != [ ]) {
-      routes = override (map makeGateway gateway);
-    } // optionalAttrs (domains != [ ]) {
-      domains = override domains;
-    };
+        }];
+      };
+    }
+  ));
 
   genericDhcpNetworks = initrd: mkIf cfg.useDHCP {
     networks."99-ethernet-default-dhcp" = {
@@ -59,23 +58,14 @@ let
       # more likely to result in interfaces being configured to
       # use DHCP when they shouldn't.
 
-      # When wait-online.anyInterface is enabled, RequiredForOnline really
-      # means "sufficient for online", so we can enable it.
-      # Otherwise, don't block the network coming online because of default networks.
       matchConfig.Name = ["en*" "eth*"];
       DHCP = "yes";
-      linkConfig.RequiredForOnline =
-        lib.mkDefault (if initrd
-        then config.boot.initrd.systemd.network.wait-online.anyInterface
-        else config.systemd.network.wait-online.anyInterface);
       networkConfig.IPv6PrivacyExtensions = "kernel";
     };
     networks."99-wireless-client-dhcp" = {
       # Like above, but this is much more likely to be correct.
       matchConfig.WLANInterfaceType = "station";
       DHCP = "yes";
-      linkConfig.RequiredForOnline =
-        lib.mkDefault config.systemd.network.wait-online.anyInterface;
       networkConfig.IPv6PrivacyExtensions = "kernel";
       # We also set the route metric to one more than the default
       # of 1024, so that Ethernet is preferred if both are
@@ -98,10 +88,10 @@ let
         };
       };
     });
-    networks."40-${i.name}" = mkMerge [ (genericNetwork id) {
+    networks."40-${i.name}" = {
       name = mkDefault i.name;
       DHCP = mkForce (dhcpStr
-        (if i.useDHCP != null then i.useDHCP else false));
+        (if i.useDHCP != null then i.useDHCP else (config.networking.useDHCP && i.ipv4.addresses == [ ])));
       address = forEach (interfaceIps i)
         (ip: "${ip.address}/${toString ip.prefixLength}");
       routes = forEach (interfaceRoutes i)
@@ -170,7 +160,7 @@ let
       } // optionalAttrs (i.mtu != null) {
         MTUBytes = toString i.mtu;
       };
-    }];
+    };
   }));
 
   bridgeNetworks = mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
@@ -181,10 +171,10 @@ let
       };
     };
     networks = listToAttrs (forEach bridge.interfaces (bi:
-      nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
+      nameValuePair "40-${bi}" {
         DHCP = mkOverride 0 (dhcpStr false);
         networkConfig.Bridge = name;
-      } ])));
+      }));
   }));
 
   vlanNetworks = mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
@@ -195,9 +185,9 @@ let
       };
       vlanConfig.Id = vlan.id;
     };
-    networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+    networks."40-${vlan.interface}" = {
       vlan = [ name ];
-    } ]);
+    };
   }));
 
 in
@@ -210,6 +200,7 @@ in
     # initrd.systemd.network.enable. By setting the latter and not the
     # former, the user retains full control over the configuration.
     boot.initrd.systemd.network = mkMerge [
+      defaultGateways
       (genericDhcpNetworks true)
       interfaceNetworks
       bridgeNetworks
@@ -226,11 +217,11 @@ in
       assertion = cfg.defaultGatewayWindowSize == null;
       message = "networking.defaultGatewayWindowSize is not supported by networkd.";
     } {
-      assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
-      message = "networking.defaultGateway.interface is not supported by networkd.";
+      assertion = cfg.defaultGateway != null -> cfg.defaultGateway.interface != null;
+      message = "networking.defaultGateway.interface is not optional when using networkd.";
     } {
-      assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
-      message = "networking.defaultGateway6.interface is not supported by networkd.";
+      assertion = cfg.defaultGateway6 != null -> cfg.defaultGateway6.interface != null;
+      message = "networking.defaultGateway6.interface is not optional when using networkd.";
     } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
       assertion = !rstp;
       message = "networking.bridges.${n}.rstp is not supported by networkd.";
@@ -245,6 +236,7 @@ in
       mkMerge [ {
         enable = true;
       }
+      defaultGateways
       (genericDhcpNetworks false)
       interfaceNetworks
       bridgeNetworks
@@ -314,10 +306,10 @@ in
         };
 
         networks = listToAttrs (forEach bond.interfaces (bi:
-          nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
+          nameValuePair "40-${bi}" {
             DHCP = mkOverride 0 (dhcpStr false);
             networkConfig.Bond = name;
-          } ])));
+          }));
       })))
       (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
         netdevs."40-${name}" = {
@@ -327,9 +319,9 @@ in
           };
           macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
         };
-        networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+        networks."40-${macvlan.interface}" = {
           macvlan = [ name ];
-        } ]);
+        };
       })))
       (mkMerge (flip mapAttrsToList cfg.fooOverUDP (name: fou: {
         netdevs."40-${name}" = {
@@ -374,9 +366,9 @@ in
               })));
         };
         networks = mkIf (sit.dev != null) {
-          "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+          "40-${sit.dev}" = {
             tunnel = [ name ];
-          } ]);
+          };
         };
       })))
       (mkMerge (flip mapAttrsToList cfg.greTunnels (name: gre: {
@@ -395,9 +387,9 @@ in
             });
         };
         networks = mkIf (gre.dev != null) {
-          "40-${gre.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+          "40-${gre.dev}" = {
             tunnel = [ name ];
-          } ]);
+          };
         };
       })))
       vlanNetworks
@@ -450,7 +442,7 @@ in
             postStop = ''
               echo "Cleaning Open vSwitch ${n}"
               echo "Shutting down internal ${n} interface"
-              ip link set ${n} down || true
+              ip link set dev ${n} down || true
               echo "Deleting flows for ${n}"
               ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
               echo "Deleting Open vSwitch ${n}"
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 0d4033ca943..298add13437 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -190,9 +190,11 @@ let
         type = types.nullOr types.bool;
         default = null;
         description = lib.mdDoc ''
-          Whether this interface should be configured with dhcp.
-          Null implies the old behavior which depends on whether ip addresses
-          are specified or not.
+          Whether this interface should be configured with DHCP. Overrides the
+          default set by {option}`networking.useDHCP`. If `null` (the default),
+          DHCP is enabled if the interface has no IPv4 addresses configured
+          with {option}`networking.interfaces.<name>.ipv4.addresses`, and
+          disabled otherwise.
         '';
       };
 
@@ -327,6 +329,24 @@ let
           default = false;
           description = lib.mdDoc "Whether to enable wol on this interface.";
         };
+        policy = mkOption {
+          type = with types; listOf (
+            enum ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon"]
+          );
+          default = ["magic"];
+          description = lib.mdDoc ''
+            The [Wake-on-LAN policy](https://www.freedesktop.org/software/systemd/man/systemd.link.html#WakeOnLan=)
+            to set for the device.
+
+            The options are
+            - `phy`: Wake on PHY activity
+            - `unicast`: Wake on unicast messages
+            - `multicast`: Wake on multicast messages
+            - `broadcast`: Wake on broadcast messages
+            - `arp`: Wake on ARP
+            - `magic`: Wake on receipt of a magic packet
+          '';
+        };
       };
     };
 
@@ -478,7 +498,7 @@ in
         option will result in an evaluation error if the hostname is empty or
         no domain is specified.
 
-        Modules that accept a mere `networing.hostName` but prefer a fully qualified
+        Modules that accept a mere `networking.hostName` but prefer a fully qualified
         domain name may use `networking.fqdnOrHostName` instead.
       '';
     };
@@ -622,9 +642,7 @@ in
           } ];
         };
       description = lib.mdDoc ''
-        The configuration for each network interface.  If
-        {option}`networking.useDHCP` is true, then every
-        interface not listed here will be configured using DHCP.
+        The configuration for each network interface.
 
         Please note that {option}`systemd.network.netdevs` has more features
         and is better maintained. When building new things, it is advised to
@@ -1286,8 +1304,8 @@ in
       default = true;
       description = lib.mdDoc ''
         Whether to use DHCP to obtain an IP address and other
-        configuration for all network interfaces that are not manually
-        configured.
+        configuration for all network interfaces that do not have any manually
+        configured IPv4 addresses.
       '';
     };
 
@@ -1326,7 +1344,10 @@ in
 
   config = {
 
-    warnings = concatMap (i: i.warnings) interfaces;
+    warnings = (concatMap (i: i.warnings) interfaces) ++ (lib.optional
+      (config.systemd.network.enable && cfg.useDHCP && !cfg.useNetworkd) ''
+        The combination of `systemd.network.enable = true`, `networking.useDHCP = true` and `networking.useNetworkd = false` can cause both networkd and dhcpcd to manage the same interfaces. This can lead to loss of networking. It is recommended you choose only one of networkd (by also enabling `networking.useNetworkd`) or scripting (by disabling `systemd.network.enable`)
+      '');
 
     assertions =
       (forEach interfaces (i: {
@@ -1385,40 +1406,12 @@ in
           val = tempaddrValues.${opt}.sysctl;
          in nameValuePair "net.ipv6.conf.${replaceStrings ["."] ["/"] i.name}.use_tempaddr" val));
 
-    security.wrappers = {
-      ping = {
-        owner = "root";
-        group = "root";
-        capabilities = "cap_net_raw+p";
-        source = "${pkgs.iputils.out}/bin/ping";
-      };
+    systemd.services.domainname = lib.mkIf (cfg.domain != null) {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.ExecStart = ''${pkgs.nettools}/bin/domainname "${cfg.domain}"'';
     };
-    security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter ''
-      /run/wrappers/bin/ping {
-        include <abstractions/base>
-        include <nixos/security.wrappers/ping>
-        rpx /run/wrappers/wrappers.*/ping,
-      }
-      /run/wrappers/wrappers.*/ping {
-        include <abstractions/base>
-        include <nixos/security.wrappers/ping>
-        capability net_raw,
-        capability setpcap,
-      }
-    '');
-
-    # Set the host and domain names in the activation script.  Don't
-    # clear it if it's not configured in the NixOS configuration,
-    # since it may have been set by dhcpcd in the meantime.
-    system.activationScripts.hostname = let
-        effectiveHostname = config.boot.kernel.sysctl."kernel.hostname" or cfg.hostName;
-      in optionalString (effectiveHostname != "") ''
-        hostname "${effectiveHostname}"
-      '';
-    system.activationScripts.domain =
-      optionalString (cfg.domain != null) ''
-        domainname "${cfg.domain}"
-      '';
 
     environment.etc.hostid = mkIf (cfg.hostId != null) { source = hostidFile; };
     boot.initrd.systemd.contents."/etc/hostid" = mkIf (cfg.hostId != null) { source = hostidFile; };
@@ -1442,6 +1435,16 @@ in
       ]
       ++ bridgeStp;
 
+    # Wake-on-LAN configuration is shared by the scripted and networkd backends.
+    systemd.network.links = pipe interfaces [
+      (filter (i: i.wakeOnLan.enable))
+      (map (i: nameValuePair "40-${i.name}" {
+        matchConfig.OriginalName = i.name;
+        linkConfig.WakeOnLan = concatStringsSep " " i.wakeOnLan.policy;
+      }))
+      listToAttrs
+    ];
+
     # The network-interfaces target is kept for backwards compatibility.
     # New modules must NOT use it.
     systemd.targets.network-interfaces =
diff --git a/nixos/modules/tasks/swraid.nix b/nixos/modules/tasks/swraid.nix
index 61b3682e0f6..249755bc054 100644
--- a/nixos/modules/tasks/swraid.nix
+++ b/nixos/modules/tasks/swraid.nix
@@ -62,13 +62,13 @@ in {
         cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
       '';
 
-      extraUtilsCommands = ''
+      extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         # Add RAID mdadm tool.
         copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
         copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon
       '';
 
-      extraUtilsCommandsTest = ''
+      extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         $out/bin/mdadm --version
       '';
 
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index 6dc4091bad1..9ee77cd79a9 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -6,49 +6,124 @@
 with lib;
 
 let
+  cfg = config.testing;
+
   qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
+
+  backdoorService = {
+    requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+    after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+    script =
+      ''
+        export USER=root
+        export HOME=/root
+        export DISPLAY=:0.0
+
+        if [[ -e /etc/profile ]]; then
+            source /etc/profile
+        fi
+
+        # Don't use a pager when executing backdoor
+        # actions. Because we use a tty, commands like systemctl
+        # or nix-store get confused into thinking they're running
+        # interactively.
+        export PAGER=
+
+        cd /tmp
+        exec < /dev/hvc0 > /dev/hvc0
+        while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
+        echo "connecting to host..." >&2
+        stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
+        # The following line is essential since it signals to
+        # the test driver that the shell is ready.
+        # See: the connect method in the Machine class.
+        echo "Spawning backdoor root shell..."
+        # Passing the terminal device makes bash run non-interactively.
+        # Otherwise we get errors on the terminal because bash tries to
+        # setup things like job control.
+        # Note: calling bash explicitly here instead of sh makes sure that
+        # we can also run non-NixOS guests during tests.
+        PS1= exec /usr/bin/env bash --norc /dev/hvc0
+      '';
+      serviceConfig.KillSignal = "SIGHUP";
+  };
+
 in
 
 {
 
+  options.testing = {
+
+    initrdBackdoor = lib.mkEnableOption (lib.mdDoc ''
+      enable backdoor.service in initrd. Requires
+      boot.initrd.systemd.enable to be enabled. Boot will pause in
+      stage 1 at initrd.target, and will listen for commands from the
+      Machine python interface, just like stage 2 normally does. This
+      enables commands to be sent to test and debug stage 1. Use
+      machine.switch_root() to leave stage 1 and proceed to stage 2.
+    '');
+
+  };
+
   config = {
 
-    systemd.services.backdoor =
-      { wantedBy = [ "multi-user.target" ];
-        requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
-        after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
-        script =
-          ''
-            export USER=root
-            export HOME=/root
-            export DISPLAY=:0.0
+    assertions = [
+      {
+        assertion = cfg.initrdBackdoor -> config.boot.initrd.systemd.enable;
+        message = ''
+          testing.initrdBackdoor requires boot.initrd.systemd.enable to be enabled.
+        '';
+      }
+    ];
 
-            source /etc/profile
+    systemd.services.backdoor = lib.mkMerge [
+      backdoorService
+      {
+        wantedBy = [ "multi-user.target" ];
+      }
+    ];
 
-            # Don't use a pager when executing backdoor
-            # actions. Because we use a tty, commands like systemctl
-            # or nix-store get confused into thinking they're running
-            # interactively.
-            export PAGER=
-
-            cd /tmp
-            exec < /dev/hvc0 > /dev/hvc0
-            while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
-            echo "connecting to host..." >&2
-            stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
-            # The following line is essential since it signals to
-            # the test driver that the shell is ready.
-            # See: the connect method in the Machine class.
-            echo "Spawning backdoor root shell..."
-            # Passing the terminal device makes bash run non-interactively.
-            # Otherwise we get errors on the terminal because bash tries to
-            # setup things like job control.
-            # Note: calling bash explicitly here instead of sh makes sure that
-            # we can also run non-NixOS guests during tests.
-            PS1= exec /usr/bin/env bash --norc /dev/hvc0
-          '';
-        serviceConfig.KillSignal = "SIGHUP";
-      };
+    boot.initrd.systemd = lib.mkMerge [
+      {
+        contents."/etc/systemd/journald.conf".text = ''
+          [Journal]
+          ForwardToConsole=yes
+          MaxLevelConsole=debug
+        '';
+
+        extraConfig = config.systemd.extraConfig;
+      }
+
+      (lib.mkIf cfg.initrdBackdoor {
+        # Implemented in machine.switch_root(). Suppress the unit by
+        # making it a noop without removing it, which would break
+        # initrd-parse-etc.service
+        services.initrd-cleanup.serviceConfig.ExecStart = [
+          # Reset
+          ""
+          # noop
+          "/bin/true"
+        ];
+
+        services.backdoor = lib.mkMerge [
+          backdoorService
+          {
+            # TODO: Both stage 1 and stage 2 should use these same
+            # settings. But a lot of existing tests rely on
+            # backdoor.service having default orderings,
+            # e.g. systemd-boot.update relies on /boot being mounted
+            # as soon as backdoor starts. But it can be useful for
+            # backdoor to start even earlier.
+            wantedBy = [ "sysinit.target" ];
+            unitConfig.DefaultDependencies = false;
+            conflicts = [ "shutdown.target" "initrd-switch-root.target" ];
+            before = [ "shutdown.target" "initrd-switch-root.target" ];
+          }
+        ];
+
+        contents."/usr/bin/env".source = "${pkgs.coreutils}/bin/env";
+      })
+    ];
 
     # Prevent agetty from being instantiated on the serial device, since it
     # interferes with the backdoor (writes to it will randomly fail
@@ -104,12 +179,6 @@ in
         MaxLevelConsole=debug
       '';
 
-    boot.initrd.systemd.contents."/etc/systemd/journald.conf".text = ''
-      [Journal]
-      ForwardToConsole=yes
-      MaxLevelConsole=debug
-    '';
-
     systemd.extraConfig = ''
       # Don't clobber the console with duplicate systemd messages.
       ShowStatus=no
@@ -123,12 +192,10 @@ in
       DefaultDeviceTimeoutSec=300
     '';
 
-    boot.initrd.systemd.extraConfig = config.systemd.extraConfig;
-
     boot.consoleLogLevel = 7;
 
     # Prevent tests from accessing the Internet.
-    networking.defaultGateway = mkOverride 150 "";
+    networking.defaultGateway = mkOverride 150 null;
     networking.nameservers = mkOverride 150 [ ];
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index 6e6021cf80f..e712fac17a4 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -61,7 +61,7 @@ in
 
         # Which provisioning agent to use. Supported values are "auto" (default), "waagent",
         # "cloud-init", or "disabled".
-        Provisioning.Agent=disabled
+        Provisioning.Agent=auto
 
         # Password authentication for root account will be unavailable.
         Provisioning.DeleteRootPassword=n
@@ -241,7 +241,16 @@ in
       after = [ "network-online.target" "sshd.service" ];
       wants = [ "network-online.target" ];
 
-      path = [ pkgs.e2fsprogs pkgs.bash ];
+      path = [
+        pkgs.e2fsprogs
+        pkgs.bash
+
+        # waagent's Microsoft.OSTCExtensions.VMAccessForLinux needs Python 3
+        pkgs.python39
+
+        # waagent's Microsoft.CPlat.Core.RunCommandLinux needs lsof
+        pkgs.lsof
+      ];
       description = "Windows Azure Agent Service";
       unitConfig.ConditionPathExists = "/etc/waagent.conf";
       serviceConfig = {
@@ -250,5 +259,10 @@ in
       };
     };
 
+    # waagent will generate files under /etc/sudoers.d during provisioning
+    security.sudo.extraConfig = ''
+      #includedir /etc/sudoers.d
+    '';
+
   };
 }
diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix
index 17cfd393830..d909680cca1 100644
--- a/nixos/modules/virtualisation/azure-image.nix
+++ b/nixos/modules/virtualisation/azure-image.nix
@@ -16,6 +16,13 @@ in
         Size of disk image. Unit is MB.
       '';
     };
+    virtualisation.azureImage.contents = mkOption {
+      type = with types; listOf attrs;
+      default = [ ];
+      description = lib.mdDoc ''
+        Extra contents to add to the image.
+      '';
+    };
   };
   config = {
     system.build.azureImage = import ../../lib/make-disk-image.nix {
@@ -26,46 +33,9 @@ in
       '';
       configFile = ./azure-config-user.nix;
       format = "raw";
-      inherit (cfg) diskSize;
+      inherit (cfg) diskSize contents;
       inherit config lib pkgs;
     };
 
-    # Azure metadata is available as a CD-ROM drive.
-    fileSystems."/metadata".device = "/dev/sr0";
-
-    systemd.services.fetch-ssh-keys = {
-      description = "Fetch host keys and authorized_keys for root user";
-
-      wantedBy = [ "sshd.service" "waagent.service" ];
-      before = [ "sshd.service" "waagent.service" ];
-
-      path  = [ pkgs.coreutils ];
-      script =
-        ''
-          eval "$(cat /metadata/CustomData.bin)"
-          if ! [ -z "$ssh_host_ecdsa_key" ]; then
-            echo "downloaded ssh_host_ecdsa_key"
-            echo "$ssh_host_ecdsa_key" > /etc/ssh/ssh_host_ed25519_key
-            chmod 600 /etc/ssh/ssh_host_ed25519_key
-          fi
-
-          if ! [ -z "$ssh_host_ecdsa_key_pub" ]; then
-            echo "downloaded ssh_host_ecdsa_key_pub"
-            echo "$ssh_host_ecdsa_key_pub" > /etc/ssh/ssh_host_ed25519_key.pub
-            chmod 644 /etc/ssh/ssh_host_ed25519_key.pub
-          fi
-
-          if ! [ -z "$ssh_root_auth_key" ]; then
-            echo "downloaded ssh_root_auth_key"
-            mkdir -m 0700 -p /root/.ssh
-            echo "$ssh_root_auth_key" > /root/.ssh/authorized_keys
-            chmod 600 /root/.ssh/authorized_keys
-          fi
-        '';
-      serviceConfig.Type = "oneshot";
-      serviceConfig.RemainAfterExit = true;
-      serviceConfig.StandardError = "journal+console";
-      serviceConfig.StandardOutput = "journal+console";
-    };
   };
 }
diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix
index 3c503f027d7..887af26949f 100644
--- a/nixos/modules/virtualisation/google-compute-config.nix
+++ b/nixos/modules/virtualisation/google-compute-config.nix
@@ -84,6 +84,10 @@ in
     { groups = [ "google-sudoers" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
   ];
 
+  security.sudo-rs.extraRules = mkIf config.users.mutableUsers [
+    { groups = [ "google-sudoers" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
+  ];
+
   users.groups.google-sudoers = mkIf config.users.mutableUsers { };
 
   boot.extraModprobeConfig = readFile "${pkgs.google-guest-configs}/etc/modprobe.d/gce-blacklist.conf";
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index 197ebb18b9a..e4a18fd81d7 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixos/modules/virtualisation/google-compute-image.nix
@@ -44,10 +44,22 @@ in
         GZIP compression level of the resulting disk image (1-9).
       '';
     };
+    virtualisation.googleComputeImage.efi = mkEnableOption "EFI booting";
   };
 
   #### implementation
   config = {
+    boot.initrd.availableKernelModules = [ "nvme" ];
+    boot.loader.grub = mkIf cfg.efi {
+      device = mkForce "nodev";
+      efiSupport = true;
+      efiInstallAsRemovable = true;
+    };
+
+    fileSystems."/boot" = mkIf cfg.efi {
+      device = "/dev/disk/by-label/ESP";
+      fsType = "vfat";
+    };
 
     system.build.googleComputeImage = import ../../lib/make-disk-image.nix {
       name = "google-compute-image";
@@ -62,6 +74,7 @@ in
       '';
       format = "raw";
       configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile;
+      partitionTableType = if cfg.efi then "efi" else "legacy";
       inherit (cfg) diskSize;
       inherit config lib pkgs;
     };
diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix
new file mode 100644
index 00000000000..3a4f0d7157a
--- /dev/null
+++ b/nixos/modules/virtualisation/incus.nix
@@ -0,0 +1,236 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.virtualisation.incus;
+  preseedFormat = pkgs.formats.yaml { };
+in
+{
+  meta.maintainers = [ lib.maintainers.adamcstephens ];
+
+  options = {
+    virtualisation.incus = {
+      enable = lib.mkEnableOption (lib.mdDoc ''
+        incusd, a daemon that manages containers and virtual machines.
+
+        Users in the "incus-admin" group can interact with
+        the daemon (e.g. to start or stop containers) using the
+        {command}`incus` command line tool, among others.
+      '');
+
+      package = lib.mkPackageOptionMD pkgs "incus" { };
+
+      lxcPackage = lib.mkPackageOptionMD pkgs "lxc" { };
+
+      preseed = lib.mkOption {
+        type = lib.types.nullOr (
+          lib.types.submodule { freeformType = preseedFormat.type; }
+        );
+
+        default = null;
+
+        description = lib.mdDoc ''
+          Configuration for Incus preseed, see
+          <https://linuxcontainers.org/incus/docs/main/howto/initialize/#non-interactive-configuration>
+          for supported values.
+
+          Changes to this will be re-applied to Incus which will overwrite existing entities or create missing ones,
+          but entities will *not* be removed by preseed.
+        '';
+
+        example = {
+          networks = [
+            {
+              name = "incusbr0";
+              type = "bridge";
+              config = {
+                "ipv4.address" = "10.0.100.1/24";
+                "ipv4.nat" = "true";
+              };
+            }
+          ];
+          profiles = [
+            {
+              name = "default";
+              devices = {
+                eth0 = {
+                  name = "eth0";
+                  network = "incusbr0";
+                  type = "nic";
+                };
+                root = {
+                  path = "/";
+                  pool = "default";
+                  size = "35GiB";
+                  type = "disk";
+                };
+              };
+            }
+          ];
+          storage_pools = [
+            {
+              name = "default";
+              driver = "dir";
+              config = {
+                source = "/var/lib/incus/storage-pools/default";
+              };
+            }
+          ];
+        };
+      };
+
+      socketActivation = lib.mkEnableOption (
+        lib.mdDoc ''
+          socket-activation for starting incus.service. Enabling this option
+          will stop incus.service from starting automatically on boot.
+        ''
+      );
+
+      startTimeout = lib.mkOption {
+        type = lib.types.ints.unsigned;
+        default = 600;
+        apply = toString;
+        description = lib.mdDoc ''
+          Time to wait (in seconds) for incusd to become ready to process requests.
+          If incusd does not reply within the configured time, `incus.service` will be
+          considered failed and systemd will attempt to restart it.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # https://github.com/lxc/incus/blob/f145309929f849b9951658ad2ba3b8f10cbe69d1/doc/reference/server_settings.md
+    boot.kernel.sysctl = {
+      "fs.aio-max-nr" = lib.mkDefault 524288;
+      "fs.inotify.max_queued_events" = lib.mkDefault 1048576;
+      "fs.inotify.max_user_instances" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix
+      "fs.inotify.max_user_watches" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix
+      "kernel.dmesg_restrict" = lib.mkDefault 1;
+      "kernel.keys.maxbytes" = lib.mkDefault 2000000;
+      "kernel.keys.maxkeys" = lib.mkDefault 2000;
+      "net.core.bpf_jit_limit" = lib.mkDefault 1000000000;
+      "net.ipv4.neigh.default.gc_thresh3" = lib.mkDefault 8192;
+      "net.ipv6.neigh.default.gc_thresh3" = lib.mkDefault 8192;
+      # vm.max_map_count is set higher in nixos/modules/config/sysctl.nix
+    };
+
+    boot.kernelModules = [
+      "veth"
+      "xt_comment"
+      "xt_CHECKSUM"
+      "xt_MASQUERADE"
+      "vhost_vsock"
+    ] ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    # Note: the following options are also declared in virtualisation.lxc, but
+    # the latter can't be simply enabled to reuse the formers, because it
+    # does a bunch of unrelated things.
+    systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
+
+    security.apparmor = {
+      packages = [ cfg.lxcPackage ];
+      policies = {
+        "bin.lxc-start".profile = ''
+          include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
+        '';
+        "lxc-containers".profile = ''
+          include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
+        '';
+      };
+    };
+
+    systemd.services.incus = {
+      description = "Incus Container and Virtual Machine Management Daemon";
+
+      wantedBy = lib.mkIf (!cfg.socketActivation) [ "multi-user.target" ];
+      after = [
+        "network-online.target"
+        "lxcfs.service"
+      ] ++ (lib.optional cfg.socketActivation "incus.socket");
+      requires = [
+        "lxcfs.service"
+      ] ++ (lib.optional cfg.socketActivation "incus.socket");
+      wants = [
+        "network-online.target"
+      ];
+
+      path = lib.mkIf config.boot.zfs.enabled [ config.boot.zfs.package ];
+
+      environment = {
+        # Override Path to the LXC template configuration directory
+        INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config";
+      };
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/incusd --group incus-admin";
+        ExecStartPost = "${cfg.package}/bin/incusd waitready --timeout=${cfg.startTimeout}";
+        ExecStop = "${cfg.package}/bin/incus admin shutdown";
+
+        KillMode = "process"; # when stopping, leave the containers alone
+        Delegate = "yes";
+        LimitMEMLOCK = "infinity";
+        LimitNOFILE = "1048576";
+        LimitNPROC = "infinity";
+        TasksMax = "infinity";
+
+        Restart = "on-failure";
+        TimeoutStartSec = "${cfg.startTimeout}s";
+        TimeoutStopSec = "30s";
+      };
+    };
+
+    systemd.sockets.incus = lib.mkIf cfg.socketActivation {
+      description = "Incus UNIX socket";
+      wantedBy = [ "sockets.target" ];
+
+      socketConfig = {
+        ListenStream = "/var/lib/incus/unix.socket";
+        SocketMode = "0660";
+        SocketGroup = "incus-admin";
+        Service = "incus.service";
+      };
+    };
+
+    systemd.services.incus-preseed = lib.mkIf (cfg.preseed != null) {
+      description = "Incus initialization with preseed file";
+
+      wantedBy = ["incus.service"];
+      after = ["incus.service"];
+      bindsTo = ["incus.service"];
+      partOf = ["incus.service"];
+
+      script = ''
+        ${cfg.package}/bin/incus admin init --preseed <${
+          preseedFormat.generate "incus-preseed.yaml" cfg.preseed
+        }
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+    };
+
+    users.groups.incus-admin = { };
+
+    users.users.root = {
+      # match documented default ranges https://linuxcontainers.org/incus/docs/main/userns-idmap/#allowed-ranges
+      subUidRanges = [
+        {
+          startUid = 1000000;
+          count = 1000000000;
+        }
+      ];
+      subGidRanges = [
+        {
+          startGid = 1000000;
+          count = 1000000000;
+        }
+      ];
+    };
+
+    virtualisation.lxc.lxcfs.enable = true;
+  };
+}
diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix
index 9402d3bf37d..61d7c4cb73f 100644
--- a/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixos/modules/virtualisation/lxc-container.nix
@@ -9,15 +9,16 @@ in {
 
   options = {
     virtualisation.lxc = {
-      privilegedContainer = lib.mkOption {
-        type = lib.types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Whether this LXC container will be running as a privileged container or not. If set to `true` then
-          additional configuration will be applied to the `systemd` instance running within the container as
-          recommended by [distrobuilder](https://linuxcontainers.org/distrobuilder/introduction/).
-        '';
-      };
+      nestedContainer = lib.mkEnableOption (lib.mdDoc ''
+        Whether this container is configured as a nested container. On LXD containers this is recommended
+        for all containers and is enabled with `security.nesting = true`.
+      '');
+
+      privilegedContainer = lib.mkEnableOption (lib.mdDoc ''
+        Whether this LXC container will be running as a privileged container or not. If set to `true` then
+        additional configuration will be applied to the `systemd` instance running within the container as
+        recommended by [distrobuilder](https://linuxcontainers.org/distrobuilder/introduction/).
+      '');
     };
   };
 
@@ -36,7 +37,6 @@ in {
         ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
       '';
 
-    # TODO: build rootfs as squashfs for faster unpack
     system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
       extraArgs = "--owner=0";
 
@@ -63,11 +63,30 @@ in {
       extraCommands = "mkdir -p proc sys dev";
     };
 
+    system.build.squashfs = pkgs.callPackage ../../lib/make-squashfs.nix {
+      fileName = "nixos-lxc-image-${pkgs.stdenv.hostPlatform.system}";
+
+      noStrip = true; # keep directory structure
+      comp = "zstd -Xcompression-level 6";
+
+      storeContents = [config.system.build.toplevel];
+
+      pseudoFiles = [
+        "/sbin d 0755 0 0"
+        "/sbin/init s 0555 0 0 ${config.system.build.toplevel}/init"
+        "/dev d 0755 0 0"
+        "/proc d 0555 0 0"
+        "/sys d 0555 0 0"
+      ];
+    };
+
     system.build.installBootLoader = pkgs.writeScript "install-lxd-sbin-init.sh" ''
       #!${pkgs.runtimeShell}
-      ln -fs "$1/init" /sbin/init
+      ${pkgs.coreutils}/bin/ln -fs "$1/init" /sbin/init
     '';
 
+    systemd.additionalUpstreamSystemUnits = lib.mkIf cfg.nestedContainer ["systemd-udev-trigger.service"];
+
     # Add the overrides from lxd distrobuilder
     # https://github.com/lxc/distrobuilder/blob/05978d0d5a72718154f1525c7d043e090ba7c3e0/distrobuilder/main.go#L630
     systemd.packages = [
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index e30fbebb662..6f628c4a6e3 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -145,9 +145,7 @@ in {
       };
 
       ui = {
-        enable = lib.mkEnableOption (lib.mdDoc ''
-          Enables the (experimental) LXD UI.
-        '');
+        enable = lib.mkEnableOption (lib.mdDoc "(experimental) LXD UI");
 
         package = lib.mkPackageOption pkgs.lxd-unwrapped "ui" { };
       };
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 5df9942dbc0..6fdb177b968 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -649,6 +649,15 @@ in
               '';
             };
 
+            restartIfChanged = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Whether the container should be restarted during a NixOS
+                configuration switch if its definition has changed.
+              '';
+            };
+
             timeoutStartSec = mkOption {
               type = types.str;
               default = "1min";
@@ -745,7 +754,7 @@ in
                   { services.postgresql.enable = true;
                     services.postgresql.package = pkgs.postgresql_14;
 
-                    system.stateVersion = "21.05";
+                    system.stateVersion = "${lib.trivial.release}";
                   };
               };
           }
@@ -826,7 +835,7 @@ in
                 containerConfig.path
                 config.environment.etc."${configurationDirectoryName}/${name}.conf".source
               ];
-              restartIfChanged = true;
+              restartIfChanged = containerConfig.restartIfChanged;
             }
           )
       )) config.containers)
@@ -897,4 +906,6 @@ in
       "tun"
     ];
   });
+
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/virtualisation/oci-common.nix b/nixos/modules/virtualisation/oci-common.nix
index ac9405e3ecf..a620df06315 100644
--- a/nixos/modules/virtualisation/oci-common.nix
+++ b/nixos/modules/virtualisation/oci-common.nix
@@ -56,5 +56,5 @@ in
 
   # Otherwise the instance may not have a working network-online.target,
   # making the fetch-ssh-keys.service fail
-  networking.useNetworkd = true;
+  networking.useNetworkd = lib.mkDefault true;
 }
diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index a9f4ab77f86..a4a40346f09 100644
--- a/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -66,6 +66,17 @@ let
           '';
         };
 
+        labels = mkOption {
+          type = with types; attrsOf str;
+          default = {};
+          description = lib.mdDoc "Labels to attach to the container at runtime.";
+          example = literalExpression ''
+            {
+              "traefik.https.routers.example.rule" = "Host(`example.container`)";
+            }
+          '';
+        };
+
         entrypoint = mkOption {
           type = with types; nullOr str;
           description = lib.mdDoc "Override the default entrypoint of the image.";
@@ -203,6 +214,13 @@ let
           '';
         };
 
+        hostname = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = lib.mdDoc "The hostname of the container.";
+          example = "hello-world";
+        };
+
         extraOptions = mkOption {
           type = with types; listOf str;
           default = [];
@@ -228,6 +246,25 @@ let
   mkService = name: container: let
     dependsOn = map (x: "${cfg.backend}-${x}.service") container.dependsOn;
     escapedName = escapeShellArg name;
+    preStartScript = pkgs.writeShellApplication {
+      name = "pre-start";
+      runtimeInputs = [ ];
+      text = ''
+        ${cfg.backend} rm -f ${name} || true
+        ${optionalString (isValidLogin container.login) ''
+          ${cfg.backend} login \
+          ${container.login.registry} \
+          --username ${container.login.username} \
+          --password-stdin < ${container.login.passwordFile}
+        ''}
+        ${optionalString (container.imageFile != null) ''
+          ${cfg.backend} load -i ${container.imageFile}
+        ''}
+        ${optionalString (cfg.backend == "podman") ''
+          rm -f /run/podman-${escapedName}.ctr-id
+        ''}
+      '';
+    };
   in {
     wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
     after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ]
@@ -242,23 +279,6 @@ let
       else if cfg.backend == "podman" then [ config.virtualisation.podman.package ]
       else throw "Unhandled backend: ${cfg.backend}";
 
-    preStart = ''
-      ${cfg.backend} rm -f ${name} || true
-      ${optionalString (isValidLogin container.login) ''
-        cat ${container.login.passwordFile} | \
-          ${cfg.backend} login \
-            ${container.login.registry} \
-            --username ${container.login.username} \
-            --password-stdin
-        ''}
-      ${optionalString (container.imageFile != null) ''
-        ${cfg.backend} load -i ${container.imageFile}
-        ''}
-      ${optionalString (cfg.backend == "podman") ''
-        rm -f /run/podman-${escapedName}.ctr-id
-        ''}
-      '';
-
     script = concatStringsSep " \\\n  " ([
       "exec ${cfg.backend} run"
       "--rm"
@@ -266,6 +286,8 @@ let
       "--log-driver=${container.log-driver}"
     ] ++ optional (container.entrypoint != null)
       "--entrypoint=${escapeShellArg container.entrypoint}"
+      ++ optional (container.hostname != null)
+      "--hostname=${escapeShellArg container.hostname}"
       ++ lib.optionals (cfg.backend == "podman") [
         "--cidfile=/run/podman-${escapedName}.ctr-id"
         "--cgroups=no-conmon"
@@ -277,6 +299,7 @@ let
       ++ map (p: "-p ${escapeShellArg p}") container.ports
       ++ optional (container.user != null) "-u ${escapeShellArg container.user}"
       ++ map (v: "-v ${escapeShellArg v}") container.volumes
+      ++ (mapAttrsToList (k: v: "-l ${escapeShellArg k}=${escapeShellArg v}") container.labels)
       ++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
       ++ map escapeShellArg container.extraOptions
       ++ [container.image]
@@ -306,7 +329,7 @@ let
       ###
       # ExecReload = ...;
       ###
-
+      ExecStartPre = [ "${preStartScript}/bin/pre-start" ];
       TimeoutStartSec = 0;
       TimeoutStopSec = 120;
       Restart = "always";
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 74c3e1ecd03..6f275baf60d 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -198,6 +198,39 @@ let
         fi
       ''}
 
+      ${lib.optionalString cfg.tpm.enable ''
+        NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}")
+        mkdir -p "$NIX_SWTPM_DIR"
+        ${lib.getExe cfg.tpm.package} \
+          socket \
+          --tpmstate dir="$NIX_SWTPM_DIR" \
+          --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \
+          --pid file="$NIX_SWTPM_DIR"/pid --daemon \
+          --tpm2 \
+          --log file="$NIX_SWTPM_DIR"/stdout,level=6
+
+        # Enable `fdflags` builtin in Bash
+        # We will need it to perform surgical modification of the file descriptor
+        # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor
+        # on exec.
+        # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec`
+        # at the end of this script. To work around that, we will just clear
+        # the `FD_CLOEXEC` bits as a first step.
+        enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags
+        # leave a dangling subprocess because the swtpm ctrl socket has
+        # "terminate" when the last connection disconnects, it stops swtpm.
+        # When qemu stops, or if the main shell process ends, the coproc will
+        # get signaled by virtue of the pipe between main and coproc ending.
+        # Which in turns triggers a socat connect-disconnect to swtpm which
+        # will stop it.
+        coproc waitingswtpm {
+          read || :
+          echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket
+        }
+        # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin.
+        fdflags -s-cloexec ''${waitingswtpm[1]}
+      ''}
+
       cd "$TMPDIR"
 
       ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"}
@@ -267,6 +300,7 @@ let
   };
 
   storeImage = import ../../lib/make-disk-image.nix {
+    name = "nix-store-image";
     inherit pkgs config lib;
     additionalPaths = [ regInfo ];
     format = "qcow2";
@@ -656,8 +690,8 @@ in
       package =
         mkOption {
           type = types.package;
-          default = hostPkgs.qemu_kvm;
-          defaultText = literalExpression "config.virtualisation.host.pkgs.qemu_kvm";
+          default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu;
+          defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu";
           example = literalExpression "pkgs.qemu_test";
           description = lib.mdDoc "QEMU package to use.";
         };
@@ -862,6 +896,32 @@ in
       };
     };
 
+    virtualisation.tpm = {
+      enable = mkEnableOption "a TPM device in the virtual machine with a driver, using swtpm.";
+
+      package = mkPackageOptionMD cfg.host.pkgs "swtpm" { };
+
+      deviceModel = mkOption {
+        type = types.str;
+        default = ({
+          "i686-linux" = "tpm-tis";
+          "x86_64-linux" = "tpm-tis";
+          "ppc64-linux" = "tpm-spapr";
+          "armv7-linux" = "tpm-tis-device";
+          "aarch64-linux" = "tpm-tis-device";
+        }.${pkgs.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU"));
+        defaultText = ''
+          Based on the guest platform Linux system:
+
+          - `tpm-tis` for (i686, x86_64)
+          - `tpm-spapr` for ppc64
+          - `tpm-tis-device` for (armv7, aarch64)
+        '';
+        example = "tpm-tis-device";
+        description = lib.mdDoc "QEMU device model for the TPM, uses the appropriate default based on th guest platform system and the package passed.";
+      };
+    };
+
     virtualisation.useDefaultFilesystems =
       mkOption {
         type = types.bool;
@@ -937,7 +997,7 @@ in
               virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max.
             '';
           }
-          { assertion = cfg.directBoot.initrd != options.virtualisation.directBoot.initrd.default -> cfg.directBoot.enable;
+          { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default;
             message =
               ''
                 You changed the default of `virtualisation.directBoot.initrd` but you are not
@@ -1027,7 +1087,8 @@ in
 
     boot.initrd.availableKernelModules =
       optional cfg.writableStore "overlay"
-      ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx";
+      ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx"
+      ++ optional (cfg.tpm.enable) "tpm_tis";
 
     virtualisation.additionalPaths = [ config.system.build.toplevel ];
 
@@ -1098,6 +1159,11 @@ in
       (mkIf (!cfg.graphics) [
         "-nographic"
       ])
+      (mkIf (cfg.tpm.enable) [
+        "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket"
+        "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm"
+        "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0"
+      ])
     ];
 
     virtualisation.qemu.drives = mkMerge [
@@ -1121,11 +1187,12 @@ in
       }) cfg.emptyDiskImages)
     ];
 
-    # Use mkVMOverride to enable building test VMs (e.g. via `nixos-rebuild
-    # build-vm`) of a system configuration, where the regular value for the
-    # `fileSystems' attribute should be disregarded (since those filesystems
-    # don't necessarily exist in the VM).
-    fileSystems = mkVMOverride cfg.fileSystems;
+    # By default, use mkVMOverride to enable building test VMs (e.g. via
+    # `nixos-rebuild build-vm`) of a system configuration, where the regular
+    # value for the `fileSystems' attribute should be disregarded (since those
+    # filesystems don't necessarily exist in the VM). You can disable this
+    # override by setting `virtualisation.fileSystems = lib.mkForce { };`.
+    fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems);
 
     virtualisation.fileSystems = let
       mkSharedDir = tag: share:
diff --git a/nixos/modules/virtualisation/vagrant-guest.nix b/nixos/modules/virtualisation/vagrant-guest.nix
index 263b1ebca08..2fad376086e 100644
--- a/nixos/modules/virtualisation/vagrant-guest.nix
+++ b/nixos/modules/virtualisation/vagrant-guest.nix
@@ -55,4 +55,5 @@ in
   };
 
   security.sudo.wheelNeedsPassword = false;
+  security.sudo-rs.wheelNeedsPassword = false;
 }
diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix
index b1565a09682..9741ea090f7 100644
--- a/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/nixos/modules/virtualisation/virtualbox-host.nix
@@ -91,7 +91,7 @@ in
   };
 
   config = mkIf cfg.enable (mkMerge [{
-    warnings = mkIf (config.nixpkgs.config.virtualbox.enableExtensionPack or false)
+    warnings = mkIf (pkgs.config.virtualbox.enableExtensionPack or false)
       ["'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'"];
     boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ];
     boot.extraModulePackages = [ kernelModules ];