about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md4
-rw-r--r--nixos/doc/manual/development/settings-options.section.md14
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.section.md36
-rw-r--r--nixos/doc/manual/from_md/development/option-declarations.section.xml4
-rw-r--r--nixos/doc/manual/from_md/development/settings-options.section.xml32
-rw-r--r--nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml43
-rw-r--r--nixos/doc/manual/from_md/installation/installing-usb.section.xml2
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2205.section.xml136
-rw-r--r--nixos/doc/manual/installation/installing-usb.section.md2
-rw-r--r--nixos/doc/manual/man-pages.xml9
-rw-r--r--nixos/doc/manual/release-notes/rl-2205.section.md33
-rw-r--r--nixos/lib/systemd-lib.nix84
-rw-r--r--nixos/lib/systemd-types.nix37
-rw-r--r--nixos/lib/systemd-unit-options.nix415
-rw-r--r--nixos/lib/testing-python.nix1
-rw-r--r--nixos/lib/utils.nix1
-rw-r--r--nixos/modules/misc/version.nix40
-rw-r--r--nixos/modules/module-list.nix4
-rw-r--r--nixos/modules/programs/_1password-gui.nix63
-rw-r--r--nixos/modules/programs/_1password.nix44
-rw-r--r--nixos/modules/services/backup/zrepl.nix3
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/slave.nix16
-rw-r--r--nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json17
-rw-r--r--nixos/modules/services/desktops/pipewire/wireplumber.nix2
-rw-r--r--nixos/modules/services/games/factorio.nix9
-rw-r--r--nixos/modules/services/hardware/joycond.nix8
-rw-r--r--nixos/modules/services/logging/logrotate.nix278
-rw-r--r--nixos/modules/services/matrix/matrix-synapse.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.nix16
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix4
-rw-r--r--nixos/modules/services/misc/nix-gc.nix2
-rw-r--r--nixos/modules/services/misc/paperless-ng.nix4
-rw-r--r--nixos/modules/services/monitoring/collectd.nix56
-rw-r--r--nixos/modules/services/monitoring/grafana.nix5
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bird.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/kea.nix4
-rw-r--r--nixos/modules/services/networking/dhcpd.nix8
-rw-r--r--nixos/modules/services/networking/kea.nix82
-rw-r--r--nixos/modules/services/networking/lxd-image-server.nix18
-rw-r--r--nixos/modules/services/networking/powerdns.nix4
-rw-r--r--nixos/modules/services/networking/syncplay.nix18
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix3
-rw-r--r--nixos/modules/services/security/sslmate-agent.nix32
-rw-r--r--nixos/modules/services/video/unifi-video.nix168
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix2
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix4
-rw-r--r--nixos/modules/services/web-apps/netbox.nix265
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix21
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix17
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix2
-rw-r--r--nixos/modules/system/boot/kernel.nix2
-rw-r--r--nixos/modules/system/boot/luksroot.nix2
-rw-r--r--nixos/modules/system/boot/networkd.nix53
-rw-r--r--nixos/modules/system/boot/stage-1.nix8
-rwxr-xr-xnixos/modules/system/boot/stage-2-init.sh61
-rw-r--r--nixos/modules/system/boot/stage-2.nix41
-rw-r--r--nixos/modules/system/boot/systemd.nix84
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix417
-rw-r--r--nixos/modules/system/boot/systemd/nspawn.nix8
-rw-r--r--nixos/modules/system/boot/systemd/tmpfiles.nix17
-rw-r--r--nixos/modules/system/boot/systemd/user.nix31
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix2
-rw-r--r--nixos/modules/tasks/filesystems.nix31
-rw-r--r--nixos/modules/tasks/network-interfaces.nix2
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix14
-rw-r--r--nixos/modules/virtualisation/podman/default.nix5
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix29
-rw-r--r--nixos/tests/aesmd.nix2
-rw-r--r--nixos/tests/agda.nix2
-rw-r--r--nixos/tests/airsonic.nix2
-rw-r--r--nixos/tests/all-tests.nix5
-rw-r--r--nixos/tests/amazon-init-shell.nix2
-rw-r--r--nixos/tests/apfs.nix2
-rw-r--r--nixos/tests/apparmor.nix2
-rw-r--r--nixos/tests/atd.nix2
-rw-r--r--nixos/tests/atop.nix12
-rw-r--r--nixos/tests/bcachefs.nix2
-rw-r--r--nixos/tests/beanstalkd.nix2
-rw-r--r--nixos/tests/bees.nix2
-rw-r--r--nixos/tests/bind.nix2
-rw-r--r--nixos/tests/bitcoind.nix2
-rw-r--r--nixos/tests/blockbook-frontend.nix2
-rw-r--r--nixos/tests/boot-stage1.nix2
-rw-r--r--nixos/tests/boot.nix6
-rw-r--r--nixos/tests/botamusique.nix4
-rw-r--r--nixos/tests/bpf.nix2
-rw-r--r--nixos/tests/breitbandmessung.nix2
-rw-r--r--nixos/tests/brscan5.nix2
-rw-r--r--nixos/tests/buildkite-agents.nix2
-rw-r--r--nixos/tests/cage.nix2
-rw-r--r--nixos/tests/cagebreak.nix2
-rw-r--r--nixos/tests/cfssl.nix2
-rw-r--r--nixos/tests/clickhouse.nix2
-rw-r--r--nixos/tests/cloud-init.nix2
-rw-r--r--nixos/tests/cntr.nix2
-rw-r--r--nixos/tests/collectd.nix9
-rw-r--r--nixos/tests/containers-bridge.nix2
-rw-r--r--nixos/tests/containers-custom-pkgs.nix2
-rw-r--r--nixos/tests/containers-ephemeral.nix2
-rw-r--r--nixos/tests/containers-extra_veth.nix2
-rw-r--r--nixos/tests/containers-hosts.nix2
-rw-r--r--nixos/tests/containers-imperative.nix2
-rw-r--r--nixos/tests/containers-ip.nix2
-rw-r--r--nixos/tests/containers-names.nix2
-rw-r--r--nixos/tests/containers-nested.nix2
-rw-r--r--nixos/tests/containers-portforward.nix2
-rw-r--r--nixos/tests/containers-tmpfs.nix2
-rw-r--r--nixos/tests/custom-ca.nix2
-rw-r--r--nixos/tests/disable-installer-tools.nix2
-rw-r--r--nixos/tests/dnsdist.nix2
-rw-r--r--nixos/tests/doas.nix2
-rw-r--r--nixos/tests/documize.nix2
-rw-r--r--nixos/tests/domination.nix2
-rw-r--r--nixos/tests/dovecot.nix2
-rw-r--r--nixos/tests/ecryptfs.nix2
-rw-r--r--nixos/tests/emacs-daemon.nix2
-rw-r--r--nixos/tests/enlightenment.nix2
-rw-r--r--nixos/tests/env.nix2
-rw-r--r--nixos/tests/etebase-server.nix2
-rw-r--r--nixos/tests/etesync-dav.nix2
-rw-r--r--nixos/tests/fancontrol.nix2
-rw-r--r--nixos/tests/fcitx/default.nix2
-rw-r--r--nixos/tests/firefox.nix2
-rw-r--r--nixos/tests/fish.nix2
-rw-r--r--nixos/tests/fluentd.nix2
-rw-r--r--nixos/tests/fontconfig-default-fonts.nix2
-rw-r--r--nixos/tests/fsck.nix2
-rw-r--r--nixos/tests/ft2-clone.nix2
-rw-r--r--nixos/tests/geth.nix2
-rw-r--r--nixos/tests/gitlab.nix18
-rw-r--r--nixos/tests/gnome-xorg.nix2
-rw-r--r--nixos/tests/gnome.nix2
-rw-r--r--nixos/tests/gotify-server.nix2
-rw-r--r--nixos/tests/graylog.nix2
-rw-r--r--nixos/tests/grocy.nix2
-rw-r--r--nixos/tests/grub.nix2
-rw-r--r--nixos/tests/hardened.nix2
-rw-r--r--nixos/tests/herbstluftwm.nix2
-rw-r--r--nixos/tests/hibernate.nix7
-rw-r--r--nixos/tests/hitch/default.nix2
-rw-r--r--nixos/tests/hocker-fetchdocker/default.nix2
-rw-r--r--nixos/tests/hockeypuck.nix2
-rw-r--r--nixos/tests/hostname.nix2
-rw-r--r--nixos/tests/hound.nix2
-rw-r--r--nixos/tests/hydra/default.nix2
-rw-r--r--nixos/tests/i3wm.nix2
-rw-r--r--nixos/tests/ihatemoney/default.nix2
-rw-r--r--nixos/tests/incron.nix2
-rw-r--r--nixos/tests/initrd-network.nix2
-rw-r--r--nixos/tests/initrd-secrets.nix2
-rw-r--r--nixos/tests/input-remapper.nix2
-rw-r--r--nixos/tests/installed-tests/default.nix2
-rw-r--r--nixos/tests/invidious.nix2
-rw-r--r--nixos/tests/isso.nix2
-rw-r--r--nixos/tests/jellyfin.nix2
-rw-r--r--nixos/tests/jenkins.nix2
-rw-r--r--nixos/tests/jibri.nix2
-rw-r--r--nixos/tests/k3s-single-node-docker.nix2
-rw-r--r--nixos/tests/k3s-single-node.nix2
-rw-r--r--nixos/tests/kbd-setfont-decompress.nix2
-rw-r--r--nixos/tests/kbd-update-search-paths-patch.nix2
-rw-r--r--nixos/tests/keepassxc.nix2
-rw-r--r--nixos/tests/kerberos/heimdal.nix2
-rw-r--r--nixos/tests/kerberos/mit.nix2
-rw-r--r--nixos/tests/kernel-generic.nix2
-rw-r--r--nixos/tests/kernel-latest-ath-user-regd.nix2
-rw-r--r--nixos/tests/kexec.nix2
-rw-r--r--nixos/tests/krb5/deprecated-config.nix2
-rw-r--r--nixos/tests/krb5/example-config.nix2
-rw-r--r--nixos/tests/ksm.nix2
-rw-r--r--nixos/tests/libinput.nix2
-rw-r--r--nixos/tests/libresprite.nix2
-rw-r--r--nixos/tests/lightdm.nix2
-rw-r--r--nixos/tests/limesurvey.nix2
-rw-r--r--nixos/tests/litestream.nix2
-rw-r--r--nixos/tests/login.nix2
-rw-r--r--nixos/tests/logrotate.nix135
-rw-r--r--nixos/tests/loki.nix2
-rw-r--r--nixos/tests/lorri/default.nix2
-rw-r--r--nixos/tests/lxd-image-server.nix2
-rw-r--r--nixos/tests/lxd-image.nix2
-rw-r--r--nixos/tests/lxd-nftables.nix2
-rw-r--r--nixos/tests/lxd.nix2
-rw-r--r--nixos/tests/maestral.nix72
-rw-r--r--nixos/tests/magnetico.nix2
-rw-r--r--nixos/tests/mailcatcher.nix2
-rw-r--r--nixos/tests/mailhog.nix2
-rw-r--r--nixos/tests/matomo.nix2
-rw-r--r--nixos/tests/matrix/pantalaimon.nix2
-rw-r--r--nixos/tests/mediawiki.nix2
-rw-r--r--nixos/tests/meilisearch.nix2
-rw-r--r--nixos/tests/memcached.nix2
-rw-r--r--nixos/tests/misc.nix2
-rw-r--r--nixos/tests/mod_perl.nix2
-rw-r--r--nixos/tests/moodle.nix2
-rw-r--r--nixos/tests/mtp.nix109
-rw-r--r--nixos/tests/musescore.nix2
-rw-r--r--nixos/tests/mysql/mysql-autobackup.nix2
-rw-r--r--nixos/tests/nagios.nix2
-rw-r--r--nixos/tests/navidrome.nix2
-rw-r--r--nixos/tests/networking.nix8
-rw-r--r--nixos/tests/nginx-modsecurity.nix2
-rw-r--r--nixos/tests/nginx-pubhtml.nix2
-rw-r--r--nixos/tests/nginx-sandbox.nix2
-rw-r--r--nixos/tests/nginx-sso.nix2
-rw-r--r--nixos/tests/nginx-variants.nix2
-rw-r--r--nixos/tests/nix-serve.nix2
-rw-r--r--nixos/tests/nixos-generate-config.nix2
-rw-r--r--nixos/tests/noto-fonts.nix2
-rw-r--r--nixos/tests/novacomd.nix2
-rw-r--r--nixos/tests/oh-my-zsh.nix2
-rw-r--r--nixos/tests/openldap.nix6
-rw-r--r--nixos/tests/opentabletdriver.nix2
-rw-r--r--nixos/tests/os-prober.nix2
-rw-r--r--nixos/tests/osrm-backend.nix2
-rw-r--r--nixos/tests/overlayfs.nix2
-rw-r--r--nixos/tests/packagekit.nix2
-rw-r--r--nixos/tests/pam/pam-oath-login.nix2
-rw-r--r--nixos/tests/pam/pam-u2f.nix2
-rw-r--r--nixos/tests/pantheon.nix2
-rw-r--r--nixos/tests/php/fpm.nix2
-rw-r--r--nixos/tests/php/httpd.nix2
-rw-r--r--nixos/tests/php/pcre.nix2
-rw-r--r--nixos/tests/pict-rs.nix2
-rw-r--r--nixos/tests/plasma5-systemd-start.nix2
-rw-r--r--nixos/tests/plasma5.nix2
-rw-r--r--nixos/tests/plausible.nix2
-rw-r--r--nixos/tests/plikd.nix2
-rw-r--r--nixos/tests/plotinus.nix2
-rw-r--r--nixos/tests/postfix-raise-smtpd-tls-security-level.nix2
-rw-r--r--nixos/tests/postfix.nix2
-rw-r--r--nixos/tests/postgresql-wal-receiver.nix2
-rw-r--r--nixos/tests/postgresql.nix2
-rw-r--r--nixos/tests/power-profiles-daemon.nix2
-rw-r--r--nixos/tests/predictable-interface-names.nix2
-rw-r--r--nixos/tests/privacyidea.nix2
-rw-r--r--nixos/tests/privoxy.nix2
-rw-r--r--nixos/tests/pt2-clone.nix2
-rw-r--r--nixos/tests/pulseaudio.nix2
-rw-r--r--nixos/tests/qboot.nix2
-rw-r--r--nixos/tests/rabbitmq.nix2
-rw-r--r--nixos/tests/radicale.nix2
-rw-r--r--nixos/tests/rasdaemon.nix2
-rw-r--r--nixos/tests/redmine.nix2
-rw-r--r--nixos/tests/restart-by-activation-script.nix2
-rw-r--r--nixos/tests/retroarch.nix2
-rw-r--r--nixos/tests/riak.nix2
-rw-r--r--nixos/tests/rspamd.nix10
-rw-r--r--nixos/tests/rsyslogd.nix4
-rw-r--r--nixos/tests/sabnzbd.nix2
-rw-r--r--nixos/tests/sddm.nix4
-rw-r--r--nixos/tests/shattered-pixel-dungeon.nix2
-rw-r--r--nixos/tests/shiori.nix2
-rw-r--r--nixos/tests/signal-desktop.nix2
-rw-r--r--nixos/tests/simple.nix2
-rw-r--r--nixos/tests/snapper.nix2
-rw-r--r--nixos/tests/soapui.nix2
-rw-r--r--nixos/tests/solr.nix2
-rw-r--r--nixos/tests/sourcehut.nix2
-rw-r--r--nixos/tests/sssd-ldap.nix2
-rw-r--r--nixos/tests/sssd.nix2
-rw-r--r--nixos/tests/starship.nix2
-rw-r--r--nixos/tests/sway.nix2
-rw-r--r--nixos/tests/sympa.nix2
-rw-r--r--nixos/tests/syncthing-init.nix2
-rw-r--r--nixos/tests/syncthing-relay.nix2
-rw-r--r--nixos/tests/systemd-analyze.nix2
-rw-r--r--nixos/tests/systemd-binfmt.nix6
-rw-r--r--nixos/tests/systemd-boot.nix18
-rw-r--r--nixos/tests/systemd-confinement.nix2
-rw-r--r--nixos/tests/systemd-cryptenroll.nix2
-rw-r--r--nixos/tests/systemd-escaping.nix2
-rw-r--r--nixos/tests/systemd-initrd-simple.nix27
-rw-r--r--nixos/tests/systemd-journal.nix2
-rw-r--r--nixos/tests/systemd-machinectl.nix2
-rw-r--r--nixos/tests/systemd-misc.nix2
-rw-r--r--nixos/tests/systemd.nix6
-rw-r--r--nixos/tests/telegraf.nix2
-rw-r--r--nixos/tests/tinywl.nix2
-rw-r--r--nixos/tests/tomcat.nix2
-rw-r--r--nixos/tests/transmission.nix2
-rw-r--r--nixos/tests/tsm-client-gui.nix2
-rw-r--r--nixos/tests/tuptime.nix2
-rw-r--r--nixos/tests/turbovnc-headless-server.nix2
-rw-r--r--nixos/tests/tuxguitar.nix2
-rw-r--r--nixos/tests/udisks2.nix2
-rw-r--r--nixos/tests/usbguard.nix2
-rw-r--r--nixos/tests/user-activation-scripts.nix2
-rw-r--r--nixos/tests/uwsgi.nix2
-rw-r--r--nixos/tests/v2ray.nix2
-rw-r--r--nixos/tests/vault-postgresql.nix2
-rw-r--r--nixos/tests/vault.nix2
-rw-r--r--nixos/tests/vector.nix2
-rw-r--r--nixos/tests/vengi-tools.nix2
-rw-r--r--nixos/tests/virtualbox.nix2
-rw-r--r--nixos/tests/web-apps/netbox.nix30
-rw-r--r--nixos/tests/web-servers/unit-php.nix2
-rw-r--r--nixos/tests/wiki-js.nix2
-rw-r--r--nixos/tests/wine.nix2
-rw-r--r--nixos/tests/wmderland.nix2
-rw-r--r--nixos/tests/wpa_supplicant.nix2
-rw-r--r--nixos/tests/xfce.nix2
-rw-r--r--nixos/tests/xmonad.nix2
-rw-r--r--nixos/tests/xterm.nix2
-rw-r--r--nixos/tests/yabar.nix2
-rw-r--r--nixos/tests/zfs.nix2
-rw-r--r--nixos/tests/zigbee2mqtt.nix2
-rw-r--r--nixos/tests/zoneminder.nix2
-rw-r--r--nixos/tests/zrepl.nix66
311 files changed, 2927 insertions, 965 deletions
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index 53ecb9b3a6248..2e11218e82389 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixos/doc/manual/development/option-declarations.section.md
@@ -120,14 +120,14 @@ lib.mkOption {
 ```nix
 lib.mkPackageOption pkgs "GHC" {
   default = [ "ghc" ];
-  example = "pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])";
+  example = "pkgs.haskell.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])";
 }
 # is like
 lib.mkOption {
   type = lib.types.package;
   default = pkgs.ghc;
   defaultText = lib.literalExpression "pkgs.ghc";
-  example = lib.literalExpression "pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])";
+  example = lib.literalExpression "pkgs.haskell.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])";
   description = "The GHC package to use.";
 }
 ```
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index f9bb6ff9cc41f..d569e23adbdcb 100644
--- a/nixos/doc/manual/development/settings-options.section.md
+++ b/nixos/doc/manual/development/settings-options.section.md
@@ -32,6 +32,20 @@ type of this option should represent the format. The most common formats
 have a predefined type and string generator already declared under
 `pkgs.formats`:
 
+`pkgs.formats.javaProperties` { *`comment`* ? `"Generated with Nix"` }
+
+:   A function taking an attribute set with values
+
+    `comment`
+
+    :   A string to put at the start of the
+        file in a comment. It can have multiple
+        lines.
+
+    It returns the `type`: `attrsOf str` and a function
+    `generate` to build a Java `.properties` file, taking
+    care of the correct escaping, etc.
+
 `pkgs.formats.json` { }
 
 :   A function taking an empty attribute set (for future extensibility)
diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md
index 433e1906f7756..a9ffffe2277c2 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/nixos/doc/manual/development/writing-nixos-tests.section.md
@@ -5,15 +5,9 @@ A NixOS test is a Nix expression that has the following structure:
 ```nix
 import ./make-test-python.nix {
 
-  # Either the configuration of a single machine:
-  machine =
-    { config, pkgs, ... }:
-    { configuration…
-    };
-
-  # Or a set of machines:
+  # One or more machines:
   nodes =
-    { machine1 =
+    { machine =
         { config, pkgs, ... }: { … };
       machine2 =
         { config, pkgs, ... }: { … };
@@ -29,17 +23,16 @@ import ./make-test-python.nix {
 
 The attribute `testScript` is a bit of Python code that executes the
 test (described below). During the test, it will start one or more
-virtual machines, the configuration of which is described by the
-attribute `machine` (if you need only one machine in your test) or by
-the attribute `nodes` (if you need multiple machines). For instance,
-[`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix)
-only needs a single machine to test whether users can log in
+virtual machines, the configuration of which is described by
+the attribute `nodes`.
+
+An example of a single-node test is
+[`login.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix).
+It only needs a single machine to test whether users can log in
 on the virtual console, whether device ownership is correctly maintained
-when switching between consoles, and so on. On the other hand,
-[`nfs/simple.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix),
-which tests NFS client and server functionality in the
-Linux kernel (including whether locks are maintained across server
-crashes), requires three machines: a server and two clients.
+when switching between consoles, and so on. An interesting multi-node test is
+[`nfs/simple.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix).
+It uses two client nodes to test correct locking across server crashes.
 
 There are a few special NixOS configuration options for test VMs:
 
@@ -67,8 +60,7 @@ The test script is a sequence of Python statements that perform various
 actions, such as starting VMs, executing commands in the VMs, and so on.
 Each virtual machine is represented as an object stored in the variable
 `name` if this is also the identifier of the machine in the declarative
-config. If you didn\'t specify multiple machines using the `nodes`
-attribute, it is just `machine`. The following example starts the
+config. If you specified a node `nodes.machine`, the following example starts the
 machine, waits until it has finished booting, then executes a command
 and checks that the output is more-or-less correct:
 
@@ -79,7 +71,7 @@ if not "Linux" in machine.succeed("uname"):
   raise Exception("Wrong OS")
 ```
 
-The first line is actually unnecessary; machines are implicitly started
+The first line is technically unnecessary; machines are implicitly started
 when you first execute an action on them (such as `wait_for_unit` or
 `succeed`). If you have multiple machines, you can speed up the test by
 starting them in parallel:
@@ -303,7 +295,7 @@ For faster dev cycles it\'s also possible to disable the code-linters
 ```nix
 import ./make-test-python.nix {
   skipLint = true;
-  machine =
+  nodes.machine =
     { config, pkgs, ... }:
     { configuration…
     };
diff --git a/nixos/doc/manual/from_md/development/option-declarations.section.xml b/nixos/doc/manual/from_md/development/option-declarations.section.xml
index 0ac5e0eeca2da..91867c224107a 100644
--- a/nixos/doc/manual/from_md/development/option-declarations.section.xml
+++ b/nixos/doc/manual/from_md/development/option-declarations.section.xml
@@ -183,14 +183,14 @@ lib.mkOption {
         <programlisting language="bash">
 lib.mkPackageOption pkgs &quot;GHC&quot; {
   default = [ &quot;ghc&quot; ];
-  example = &quot;pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
+  example = &quot;pkgs.haskell.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
 }
 # is like
 lib.mkOption {
   type = lib.types.package;
   default = pkgs.ghc;
   defaultText = lib.literalExpression &quot;pkgs.ghc&quot;;
-  example = lib.literalExpression &quot;pkgs.haskell.package.ghc921.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
+  example = lib.literalExpression &quot;pkgs.haskell.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
   description = &quot;The GHC package to use.&quot;;
 }
 </programlisting>
diff --git a/nixos/doc/manual/from_md/development/settings-options.section.xml b/nixos/doc/manual/from_md/development/settings-options.section.xml
index 746011a2d075b..d26dd96243dbe 100644
--- a/nixos/doc/manual/from_md/development/settings-options.section.xml
+++ b/nixos/doc/manual/from_md/development/settings-options.section.xml
@@ -55,6 +55,38 @@
     <variablelist>
       <varlistentry>
         <term>
+          <literal>pkgs.formats.javaProperties</literal> {
+          <emphasis><literal>comment</literal></emphasis> ?
+          <literal>&quot;Generated with Nix&quot;</literal> }
+        </term>
+        <listitem>
+          <para>
+            A function taking an attribute set with values
+          </para>
+          <variablelist>
+            <varlistentry>
+              <term>
+                <literal>comment</literal>
+              </term>
+              <listitem>
+                <para>
+                  A string to put at the start of the file in a comment.
+                  It can have multiple lines.
+                </para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+          <para>
+            It returns the <literal>type</literal>:
+            <literal>attrsOf str</literal> and a function
+            <literal>generate</literal> to build a Java
+            <literal>.properties</literal> file, taking care of the
+            correct escaping, etc.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
           <literal>pkgs.formats.json</literal> { }
         </term>
         <listitem>
diff --git a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
index 4f856f98f2a22..b194d58e5beb6 100644
--- a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
+++ b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
@@ -6,15 +6,9 @@
   <programlisting language="bash">
 import ./make-test-python.nix {
 
-  # Either the configuration of a single machine:
-  machine =
-    { config, pkgs, ... }:
-    { configuration…
-    };
-
-  # Or a set of machines:
+  # One or more machines:
   nodes =
-    { machine1 =
+    { machine =
         { config, pkgs, ... }: { … };
       machine2 =
         { config, pkgs, ... }: { … };
@@ -31,18 +25,18 @@ import ./make-test-python.nix {
     The attribute <literal>testScript</literal> is a bit of Python code
     that executes the test (described below). During the test, it will
     start one or more virtual machines, the configuration of which is
-    described by the attribute <literal>machine</literal> (if you need
-    only one machine in your test) or by the attribute
-    <literal>nodes</literal> (if you need multiple machines). For
-    instance,
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix"><literal>login.nix</literal></link>
-    only needs a single machine to test whether users can log in on the
-    virtual console, whether device ownership is correctly maintained
-    when switching between consoles, and so on. On the other hand,
-    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix"><literal>nfs/simple.nix</literal></link>,
-    which tests NFS client and server functionality in the Linux kernel
-    (including whether locks are maintained across server crashes),
-    requires three machines: a server and two clients.
+    described by the attribute <literal>nodes</literal>.
+  </para>
+  <para>
+    An example of a single-node test is
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix"><literal>login.nix</literal></link>.
+    It only needs a single machine to test whether users can log in on
+    the virtual console, whether device ownership is correctly
+    maintained when switching between consoles, and so on. An
+    interesting multi-node test is
+    <link xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/nfs/simple.nix"><literal>nfs/simple.nix</literal></link>.
+    It uses two client nodes to test correct locking across server
+    crashes.
   </para>
   <para>
     There are a few special NixOS configuration options for test VMs:
@@ -94,9 +88,8 @@ import ./make-test-python.nix {
     various actions, such as starting VMs, executing commands in the
     VMs, and so on. Each virtual machine is represented as an object
     stored in the variable <literal>name</literal> if this is also the
-    identifier of the machine in the declarative config. If you didn't
-    specify multiple machines using the <literal>nodes</literal>
-    attribute, it is just <literal>machine</literal>. The following
+    identifier of the machine in the declarative config. If you
+    specified a node <literal>nodes.machine</literal>, the following
     example starts the machine, waits until it has finished booting,
     then executes a command and checks that the output is more-or-less
     correct:
@@ -108,7 +101,7 @@ if not &quot;Linux&quot; in machine.succeed(&quot;uname&quot;):
   raise Exception(&quot;Wrong OS&quot;)
 </programlisting>
   <para>
-    The first line is actually unnecessary; machines are implicitly
+    The first line is technically unnecessary; machines are implicitly
     started when you first execute an action on them (such as
     <literal>wait_for_unit</literal> or <literal>succeed</literal>). If
     you have multiple machines, you can speed up the test by starting
@@ -554,7 +547,7 @@ machine.wait_for_unit(&quot;xautolock.service&quot;, &quot;x-session-user&quot;)
     <programlisting language="bash">
 import ./make-test-python.nix {
   skipLint = true;
-  machine =
+  nodes.machine =
     { config, pkgs, ... }:
     { configuration…
     };
diff --git a/nixos/doc/manual/from_md/installation/installing-usb.section.xml b/nixos/doc/manual/from_md/installation/installing-usb.section.xml
index b46a1d565557d..df266eb16800f 100644
--- a/nixos/doc/manual/from_md/installation/installing-usb.section.xml
+++ b/nixos/doc/manual/from_md/installation/installing-usb.section.xml
@@ -17,7 +17,7 @@ $ diskutil list
 [..]
 $ diskutil unmountDisk diskN
 Unmount of all volumes on diskN was successful
-$ sudo dd if=nix.iso of=/dev/rdiskN
+$ sudo dd if=nix.iso of=/dev/rdiskN bs=1M
 </programlisting>
     <para>
       Using the 'raw' <literal>rdiskN</literal> device instead of
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 86c68fcd0ca22..38dd7b3894ddc 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -88,6 +88,14 @@
           and their users.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          The default GHC version has been updated from 8.10.7 to 9.0.2.
+          <literal>pkgs.haskellPackages</literal> and
+          <literal>pkgs.ghc</literal> will now use this version by
+          default.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-22.05-new-services">
@@ -223,6 +231,13 @@
       </listitem>
       <listitem>
         <para>
+          <link xlink:href="https://github.com/netbox-community/netbox">netbox</link>,
+          infrastructure resource modeling (IRM) tool. Available as
+          <link xlink:href="options.html#opt-services.netbox.enable">services.netbox</link>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <link xlink:href="https://tetrd.app">tetrd</link>, share your
           internet connection from your device to your PC and vice versa
           through a USB cable. Available at
@@ -840,6 +855,54 @@
       </listitem>
       <listitem>
         <para>
+          The <literal>vim.customize</literal> function produced by
+          <literal>vimUtils.makeCustomizable</literal> now has a
+          slightly different interface:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              The wrapper now includes everything in the given Vim
+              derivation if <literal>name</literal> is
+              <literal>&quot;vim&quot;</literal> (the default). This
+              makes the <literal>wrapManual</literal> argument obsolete,
+              but this behavior can be overriden by setting the
+              <literal>standalone</literal> argument.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              All the executables present in the given derivation (or,
+              in <literal>standalone</literal> mode, only the
+              <literal>*vim</literal> ones) are wrapped. This makes the
+              <literal>wrapGui</literal> argument obsolete.
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              The <literal>vimExecutableName</literal> and
+              <literal>gvimExecutableName</literal> arguments were
+              replaced by a single <literal>executableName</literal>
+              argument in which the shell variable
+              <literal>$exe</literal> can be used to refer to the
+              wrapped executable’s name.
+            </para>
+          </listitem>
+        </itemizedlist>
+        <para>
+          See the comments in
+          <literal>pkgs/applications/editors/vim/plugins/vim-utils.nix</literal>
+          for more details.
+        </para>
+        <para>
+          <literal>vimUtils.vimWithRC</literal> was removed. You should
+          instead use <literal>customize</literal> on a Vim derivation,
+          which now accepts <literal>vimrcFile</literal> and
+          <literal>gvimrcFile</literal> arguments.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <literal>tilp2</literal> was removed together with its module
         </para>
       </listitem>
@@ -962,8 +1025,8 @@
       <listitem>
         <para>
           <literal>pkgs.pgadmin</literal> now refers to
-          <literal>pkgs.pgadmin4</literal>. If you still need pgadmin3,
-          use <literal>pkgs.pgadmin3</literal>.
+          <literal>pkgs.pgadmin4</literal>. <literal>pgadmin3</literal>
+          has been removed.
         </para>
       </listitem>
       <listitem>
@@ -1205,6 +1268,13 @@
           example.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <literal>pkgs.cosmopolitan</literal> no longer provides the
+          <literal>cosmoc</literal> command. It has been moved to
+          <literal>pkgs.cosmoc</literal>.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-22.05-notable-changes">
@@ -1333,6 +1403,35 @@
       </listitem>
       <listitem>
         <para>
+          A new option group
+          <literal>systemd.network.wait-online</literal> was added, with
+          options to configure
+          <literal>systemd-networkd-wait-online.service</literal>:
+        </para>
+        <itemizedlist spacing="compact">
+          <listitem>
+            <para>
+              <literal>anyInterface</literal> allows specifying that the
+              network should be considered online when <emphasis>at
+              least one</emphasis> interface is online (useful on
+              laptops)
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>timeout</literal> defines how long to wait for
+              the network to come online
+            </para>
+          </listitem>
+          <listitem>
+            <para>
+              <literal>extraArgs</literal> for everything else
+            </para>
+          </listitem>
+        </itemizedlist>
+      </listitem>
+      <listitem>
+        <para>
           The <literal>influxdb2</literal> package was split into
           <literal>influxdb2-server</literal> and
           <literal>influxdb2-cli</literal>, matching the split that took
@@ -1365,6 +1464,15 @@
       </listitem>
       <listitem>
         <para>
+          The <literal>services.unifi-video.openPorts</literal> option
+          default value of <literal>true</literal> is now deprecated and
+          will be changed to <literal>false</literal> in 22.11.
+          Configurations using this default will print a warning when
+          rebuilt.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           <literal>security.acme</literal> certificates will now
           correctly check for CA revokation before reaching their
           minimum age.
@@ -1576,9 +1684,20 @@
       </listitem>
       <listitem>
         <para>
-          <literal>services.logrotate.enable</literal> now defaults to
-          true if any rotate path has been defined, and some paths have
-          been added by default.
+          <link linkend="opt-services.logrotate.enable">services.logrotate.enable</link>
+          now defaults to true if any rotate path has been defined, and
+          some paths have been added by default.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          The logrotate module also has been updated to freeform syntax:
+          <link linkend="opt-services.logrotate.paths">services.logrotate.paths</link>
+          and
+          <link linkend="opt-services.logrotate.extraConfig">services.logrotate.extraConfig</link>
+          will work, but issue deprecation warnings and
+          <link linkend="opt-services.logrotate.settings">services.logrotate.settings</link>
+          should now be used instead.
         </para>
       </listitem>
       <listitem>
@@ -1663,6 +1782,13 @@
       </listitem>
       <listitem>
         <para>
+          <literal>services.xserver.desktopManager.xfce</literal> now
+          includes Xfce’s screen locker,
+          <literal>xfce4-screensaver</literal>.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           The <literal>hadoop</literal> package has added support for
           <literal>aarch64-linux</literal> and
           <literal>aarch64-darwin</literal> as of 3.3.1
diff --git a/nixos/doc/manual/installation/installing-usb.section.md b/nixos/doc/manual/installation/installing-usb.section.md
index ae58c08e5237e..d893e22e6381b 100644
--- a/nixos/doc/manual/installation/installing-usb.section.md
+++ b/nixos/doc/manual/installation/installing-usb.section.md
@@ -18,7 +18,7 @@ $ diskutil list
 [..]
 $ diskutil unmountDisk diskN
 Unmount of all volumes on diskN was successful
-$ sudo dd if=nix.iso of=/dev/rdiskN
+$ sudo dd if=nix.iso of=/dev/rdiskN bs=1M
 ```
 
 Using the \'raw\' `rdiskN` device instead of `diskN` completes in
diff --git a/nixos/doc/manual/man-pages.xml b/nixos/doc/manual/man-pages.xml
index 49acfe7330b6d..58f73521e94fd 100644
--- a/nixos/doc/manual/man-pages.xml
+++ b/nixos/doc/manual/man-pages.xml
@@ -3,10 +3,15 @@
            xmlns:xi="http://www.w3.org/2001/XInclude">
  <title>NixOS Reference Pages</title>
  <info>
-  <author><personname><firstname>Eelco</firstname><surname>Dolstra</surname></personname>
+  <author>
+   <personname><firstname>Eelco</firstname><surname>Dolstra</surname></personname>
    <contrib>Author</contrib>
   </author>
-  <copyright><year>2007-2020</year><holder>Eelco Dolstra</holder>
+  <author>
+   <personname><othername>The Nixpkgs/NixOS contributors</othername></personname>
+   <contrib>Author</contrib>
+  </author>
+  <copyright><year>2007-2022</year><holder>Eelco Dolstra and the Nixpkgs/NixOS contributors</holder>
   </copyright>
  </info>
  <xi:include href="man-configuration.xml" />
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index f1bb091b89244..82f1b97d5cbdd 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -29,6 +29,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - Module authors can use `mkRenamedOptionModuleWith` to automate the deprecation cycle without annoying out-of-tree module authors and their users.
 
+- The default GHC version has been updated from 8.10.7 to 9.0.2. `pkgs.haskellPackages` and `pkgs.ghc` will now use this version by default.
+
 ## New Services {#sec-release-22.05-new-services}
 
 - [aesmd](https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw), the Intel SGX Architectural Enclave Service Manager. Available as [services.aesmd](#opt-services.aesmd.enable).
@@ -65,6 +67,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [prometheus-pve-exporter](https://github.com/prometheus-pve/prometheus-pve-exporter), a tool that exposes information from the Proxmox VE API for use by Prometheus. Available as [services.prometheus.exporters.pve](options.html#opt-services.prometheus.exporters.pve).
 
+- [netbox](https://github.com/netbox-community/netbox), infrastructure resource modeling (IRM) tool. Available as [services.netbox](options.html#opt-services.netbox.enable).
+
 - [tetrd](https://tetrd.app), share your internet connection from your device to your PC and vice versa through a USB cable. Available at [services.tetrd](#opt-services.tetrd.enable).
 
 - [agate](https://github.com/mbrubeck/agate), a very simple server for the Gemini hypertext protocol. Available as [services.agate](options.html#opt-services.agate.enable).
@@ -327,6 +331,15 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `pkgs._7zz` is now correctly licensed as LGPL3+ and BSD3 with optional unfree unRAR licensed code
 
+- The `vim.customize` function produced by `vimUtils.makeCustomizable` now has a slightly different interface:
+  * The wrapper now includes everything in the given Vim derivation if `name` is `"vim"` (the default). This makes the `wrapManual` argument obsolete, but this behavior can be overriden by setting the `standalone` argument.
+  * All the executables present in the given derivation (or, in `standalone` mode, only the `*vim` ones) are wrapped. This makes the `wrapGui` argument obsolete.
+  * The `vimExecutableName` and `gvimExecutableName` arguments were replaced by a single `executableName` argument in which the shell variable `$exe` can be used to refer to the wrapped executable's name.
+
+  See the comments in `pkgs/applications/editors/vim/plugins/vim-utils.nix` for more details.
+
+  `vimUtils.vimWithRC` was removed. You should instead use `customize` on a Vim derivation, which now accepts `vimrcFile` and `gvimrcFile` arguments.
+
 - `tilp2` was removed together with its module
 
 - The F-PROT antivirus (`fprot` package) and its service module were removed because it
@@ -364,8 +377,7 @@ In addition to numerous new and upgraded packages, this release has the followin
   you should change the package you refer to. If you don't need them update your
   commands from `otelcontribcol` to `otelcorecol` and enjoy a 7x smaller binary.
 
-- `pkgs.pgadmin` now refers to `pkgs.pgadmin4`.
-  If you still need pgadmin3, use `pkgs.pgadmin3`.
+- `pkgs.pgadmin` now refers to `pkgs.pgadmin4`. `pgadmin3` has been removed.
 
 - `pkgs.noto-fonts-cjk` is now deprecated in favor of `pkgs.noto-fonts-cjk-sans`
   and `pkgs.noto-fonts-cjk-serif` because they each have different release
@@ -435,6 +447,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
   See the `vscode` package for a more detailed example.
 
+- `pkgs.cosmopolitan` no longer provides the `cosmoc` command. It has been moved to `pkgs.cosmoc`.
+
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
 ## Other Notable Changes {#sec-release-22.05-notable-changes}
@@ -484,6 +498,11 @@ In addition to numerous new and upgraded packages, this release has the followin
   still under heavy development and behavior is not always flawless.
   Furthermore, not all Electron apps use the latest Electron versions.
 
+- A new option group `systemd.network.wait-online` was added, with options to configure `systemd-networkd-wait-online.service`:
+  - `anyInterface` allows specifying that the network should be considered online when *at least one* interface is online (useful on laptops)
+  - `timeout` defines how long to wait for the network to come online
+  - `extraArgs` for everything else
+
 - The `influxdb2` package was split into `influxdb2-server` and
   `influxdb2-cli`, matching the split that took place upstream. A
   combined `influxdb2` package is still provided in this release for
@@ -497,6 +516,9 @@ In addition to numerous new and upgraded packages, this release has the followin
 - The `services.unifi.openPorts` option default value of `true` is now deprecated and will be changed to `false` in 22.11.
   Configurations using this default will print a warning when rebuilt.
 
+- The `services.unifi-video.openPorts` option default value of `true` is now deprecated and will be changed to `false` in 22.11.
+  Configurations using this default will print a warning when rebuilt.
+
 - `security.acme` certificates will now correctly check for CA
   revokation before reaching their minimum age.
 
@@ -561,8 +583,11 @@ In addition to numerous new and upgraded packages, this release has the followin
 - `services.mattermost.plugins` has been added to allow the declarative installation of Mattermost plugins.
   Plugins are automatically repackaged using autoPatchelf.
 
-- `services.logrotate.enable` now defaults to true if any rotate path has
+- [services.logrotate.enable](#opt-services.logrotate.enable) now defaults to true if any rotate path has
   been defined, and some paths have been added by default.
+- The logrotate module also has been updated to freeform syntax: [services.logrotate.paths](#opt-services.logrotate.paths)
+  and [services.logrotate.extraConfig](#opt-services.logrotate.extraConfig) will work, but issue deprecation
+  warnings and [services.logrotate.settings](#opt-services.logrotate.settings) should now be used instead.
 
 - The `zrepl` package has been updated from 0.4.0 to 0.5:
 
@@ -590,6 +615,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The polkit service, available at `security.polkit.enable`, is now disabled by default. It will automatically be enabled through services and desktop environments as needed.
 
+- `services.xserver.desktopManager.xfce` now includes Xfce's screen locker, `xfce4-screensaver`.
+
 - The `hadoop` package has added support for `aarch64-linux` and `aarch64-darwin` as of 3.3.1 ([#158613](https://github.com/NixOS/nixpkgs/pull/158613)).
 
 - The `R` package now builds again on `aarch64-darwin` ([#158992](https://github.com/NixOS/nixpkgs/pull/158992)).
diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix
index 37900b0b16f6a..16ec47df3a464 100644
--- a/nixos/lib/systemd-lib.nix
+++ b/nixos/lib/systemd-lib.nix
@@ -122,10 +122,15 @@ in rec {
         (if isList value then value else [value]))
         as));
 
-  generateUnits = generateUnits' true;
-
-  generateUnits' = allowCollisions: type: units: upstreamUnits: upstreamWants:
-    pkgs.runCommand "${type}-units"
+  generateUnits = { allowCollisions ? true, type, units, upstreamUnits, upstreamWants, packages ? cfg.packages, package ? cfg.package }:
+    let
+      typeDir = ({
+        system = "system";
+        initrd = "system";
+        user = "user";
+        nspawn = "nspawn";
+      }).${type};
+    in pkgs.runCommand "${type}-units"
       { preferLocalBuild = true;
         allowSubstitutes = false;
       } ''
@@ -133,7 +138,7 @@ in rec {
 
       # Copy the upstream systemd units we're interested in.
       for i in ${toString upstreamUnits}; do
-        fn=${cfg.package}/example/systemd/${type}/$i
+        fn=${package}/example/systemd/${typeDir}/$i
         if ! [ -e $fn ]; then echo "missing $fn"; false; fi
         if [ -L $fn ]; then
           target="$(readlink "$fn")"
@@ -150,7 +155,7 @@ in rec {
       # Copy .wants links, but only those that point to units that
       # we're interested in.
       for i in ${toString upstreamWants}; do
-        fn=${cfg.package}/example/systemd/${type}/$i
+        fn=${package}/example/systemd/${typeDir}/$i
         if ! [ -e $fn ]; then echo "missing $fn"; false; fi
         x=$out/$(basename $fn)
         mkdir $x
@@ -162,14 +167,14 @@ in rec {
       done
 
       # Symlink all units provided listed in systemd.packages.
-      packages="${toString cfg.packages}"
+      packages="${toString packages}"
 
       # Filter duplicate directories
       declare -A unique_packages
       for k in $packages ; do unique_packages[$k]=1 ; done
 
       for i in ''${!unique_packages[@]}; do
-        for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do
+        for fn in $i/etc/systemd/${typeDir}/* $i/lib/systemd/${typeDir}/*; do
           if ! [[ "$fn" =~ .wants$ ]]; then
             if [[ -d "$fn" ]]; then
               targetDir="$out/$(basename "$fn")"
@@ -270,9 +275,9 @@ in rec {
           { Conflicts = toString config.conflicts; }
         // optionalAttrs (config.requisite != [])
           { Requisite = toString config.requisite; }
-        // optionalAttrs (config.restartTriggers != [])
+        // optionalAttrs (config ? restartTriggers && config.restartTriggers != [])
           { X-Restart-Triggers = toString config.restartTriggers; }
-        // optionalAttrs (config.reloadTriggers != [])
+        // optionalAttrs (config ? reloadTriggers && config.reloadTriggers != [])
           { X-Reload-Triggers = toString config.reloadTriggers; }
         // optionalAttrs (config.description != "") {
           Description = config.description; }
@@ -288,45 +293,24 @@ in rec {
     };
   };
 
-  serviceConfig = { name, config, ... }: {
-    config = mkMerge
-      [ { # Default path for systemd services.  Should be quite minimal.
-          path = mkAfter
-            [ pkgs.coreutils
-              pkgs.findutils
-              pkgs.gnugrep
-              pkgs.gnused
-              systemd
-            ];
-          environment.PATH = "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}";
-        }
-        (mkIf (config.preStart != "")
-          { serviceConfig.ExecStartPre =
-              [ (makeJobScript "${name}-pre-start" config.preStart) ];
-          })
-        (mkIf (config.script != "")
-          { serviceConfig.ExecStart =
-              makeJobScript "${name}-start" config.script + " " + config.scriptArgs;
-          })
-        (mkIf (config.postStart != "")
-          { serviceConfig.ExecStartPost =
-              [ (makeJobScript "${name}-post-start" config.postStart) ];
-          })
-        (mkIf (config.reload != "")
-          { serviceConfig.ExecReload =
-              makeJobScript "${name}-reload" config.reload;
-          })
-        (mkIf (config.preStop != "")
-          { serviceConfig.ExecStop =
-              makeJobScript "${name}-pre-stop" config.preStop;
-          })
-        (mkIf (config.postStop != "")
-          { serviceConfig.ExecStopPost =
-              makeJobScript "${name}-post-stop" config.postStop;
-          })
-      ];
+  serviceConfig = { config, ... }: {
+    config.environment.PATH = mkIf (config.path != []) "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}";
   };
 
+  stage2ServiceConfig = {
+    imports = [ serviceConfig ];
+    # Default path for systemd services. Should be quite minimal.
+    config.path = mkAfter [
+      pkgs.coreutils
+      pkgs.findutils
+      pkgs.gnugrep
+      pkgs.gnused
+      systemd
+    ];
+  };
+
+  stage1ServiceConfig = serviceConfig;
+
   mountConfig = { config, ... }: {
     config = {
       mountConfig =
@@ -374,12 +358,12 @@ in rec {
               # systemd max line length is now 1MiB
               # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
               in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)}
-          ${if def.reloadIfChanged then ''
+          ${if def ? reloadIfChanged && def.reloadIfChanged then ''
             X-ReloadIfChanged=true
-          '' else if !def.restartIfChanged then ''
+          '' else if (def ? restartIfChanged && !def.restartIfChanged) then ''
             X-RestartIfChanged=false
           '' else ""}
-          ${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"}
+          ${optionalString (def ? stopIfChanged && !def.stopIfChanged) "X-StopIfChanged=false"}
           ${attrsToSection def.serviceConfig}
         '';
     };
diff --git a/nixos/lib/systemd-types.nix b/nixos/lib/systemd-types.nix
new file mode 100644
index 0000000000000..71962fab2e177
--- /dev/null
+++ b/nixos/lib/systemd-types.nix
@@ -0,0 +1,37 @@
+{ lib, systemdUtils }:
+
+with systemdUtils.lib;
+with systemdUtils.unitOptions;
+with lib;
+
+rec {
+  units = with types;
+    attrsOf (submodule ({ name, config, ... }: {
+      options = concreteUnitOptions;
+      config = { unit = mkDefault (systemdUtils.lib.makeUnit name config); };
+    }));
+
+  services = with types; attrsOf (submodule [ stage2ServiceOptions unitConfig stage2ServiceConfig ]);
+  initrdServices = with types; attrsOf (submodule [ stage1ServiceOptions unitConfig stage1ServiceConfig ]);
+
+  targets = with types; attrsOf (submodule [ stage2CommonUnitOptions unitConfig ]);
+  initrdTargets = with types; attrsOf (submodule [ stage1CommonUnitOptions unitConfig ]);
+
+  sockets = with types; attrsOf (submodule [ stage2SocketOptions unitConfig ]);
+  initrdSockets = with types; attrsOf (submodule [ stage1SocketOptions unitConfig ]);
+
+  timers = with types; attrsOf (submodule [ stage2TimerOptions unitConfig ]);
+  initrdTimers = with types; attrsOf (submodule [ stage1TimerOptions unitConfig ]);
+
+  paths = with types; attrsOf (submodule [ stage2PathOptions unitConfig ]);
+  initrdPaths = with types; attrsOf (submodule [ stage1PathOptions unitConfig ]);
+
+  slices = with types; attrsOf (submodule [ stage2SliceOptions unitConfig ]);
+  initrdSlices = with types; attrsOf (submodule [ stage1SliceOptions unitConfig ]);
+
+  mounts = with types; listOf (submodule [ stage2MountOptions unitConfig mountConfig ]);
+  initrdMounts = with types; listOf (submodule [ stage1MountOptions unitConfig mountConfig ]);
+
+  automounts = with types; listOf (submodule [ stage2AutomountOptions unitConfig automountConfig ]);
+  initrdAutomounts = with types; attrsOf (submodule [ stage1AutomountOptions unitConfig automountConfig ]);
+}
diff --git a/nixos/lib/systemd-unit-options.nix b/nixos/lib/systemd-unit-options.nix
index 8029ba0e3f6cf..c9d424d391196 100644
--- a/nixos/lib/systemd-unit-options.nix
+++ b/nixos/lib/systemd-unit-options.nix
@@ -94,7 +94,7 @@ in rec {
 
   };
 
-  commonUnitOptions = sharedOptions // {
+  commonUnitOptions = { options = (sharedOptions // {
 
     description = mkOption {
       default = "";
@@ -191,27 +191,6 @@ in rec {
       '';
     };
 
-    restartTriggers = mkOption {
-      default = [];
-      type = types.listOf types.unspecified;
-      description = ''
-        An arbitrary list of items such as derivations.  If any item
-        in the list changes between reconfigurations, the service will
-        be restarted.
-      '';
-    };
-
-    reloadTriggers = mkOption {
-      default = [];
-      type = types.listOf unitOption;
-      description = ''
-        An arbitrary list of items such as derivations.  If any item
-        in the list changes between reconfigurations, the service will
-        be reloaded.  If anything but a reload trigger changes in the
-        unit file, the unit will be restarted instead.
-      '';
-    };
-
     onFailure = mkOption {
       default = [];
       type = types.listOf unitNameType;
@@ -239,10 +218,39 @@ in rec {
        '';
     };
 
+  }); };
+
+  stage2CommonUnitOptions = {
+    imports = [
+      commonUnitOptions
+    ];
+
+    options = {
+      restartTriggers = mkOption {
+        default = [];
+        type = types.listOf types.unspecified;
+        description = ''
+          An arbitrary list of items such as derivations.  If any item
+          in the list changes between reconfigurations, the service will
+          be restarted.
+        '';
+      };
+
+      reloadTriggers = mkOption {
+        default = [];
+        type = types.listOf unitOption;
+        description = ''
+          An arbitrary list of items such as derivations.  If any item
+          in the list changes between reconfigurations, the service will
+          be reloaded.  If anything but a reload trigger changes in the
+          unit file, the unit will be restarted instead.
+        '';
+      };
+    };
   };
+  stage1CommonUnitOptions = commonUnitOptions;
 
-
-  serviceOptions = commonUnitOptions // {
+  serviceOptions = { options = {
 
     environment = mkOption {
       default = {};
@@ -276,121 +284,164 @@ in rec {
       '';
     };
 
-    script = mkOption {
-      type = types.lines;
-      default = "";
-      description = "Shell commands executed as the service's main process.";
-    };
-
-    scriptArgs = mkOption {
-      type = types.str;
-      default = "";
-      description = "Arguments passed to the main process script.";
-    };
-
-    preStart = mkOption {
-      type = types.lines;
-      default = "";
-      description = ''
-        Shell commands executed before the service's main process
-        is started.
-      '';
-    };
-
-    postStart = mkOption {
-      type = types.lines;
-      default = "";
-      description = ''
-        Shell commands executed after the service's main process
-        is started.
-      '';
-    };
-
-    reload = mkOption {
-      type = types.lines;
-      default = "";
-      description = ''
-        Shell commands executed when the service's main process
-        is reloaded.
-      '';
-    };
-
-    preStop = mkOption {
-      type = types.lines;
-      default = "";
-      description = ''
-        Shell commands executed to stop the service.
-      '';
-    };
-
-    postStop = mkOption {
-      type = types.lines;
-      default = "";
-      description = ''
-        Shell commands executed after the service's main process
-        has exited.
-      '';
-    };
-
-    restartIfChanged = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Whether the service should be restarted during a NixOS
-        configuration switch if its definition has changed.
-      '';
-    };
-
-    reloadIfChanged = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Whether the service should be reloaded during a NixOS
-        configuration switch if its definition has changed.  If
-        enabled, the value of <option>restartIfChanged</option> is
-        ignored.
-
-        This option should not be used anymore in favor of
-        <option>reloadTriggers</option> which allows more granular
-        control of when a service is reloaded and when a service
-        is restarted.
-      '';
-    };
-
-    stopIfChanged = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        If set, a changed unit is restarted by calling
-        <command>systemctl stop</command> in the old configuration,
-        then <command>systemctl start</command> in the new one.
-        Otherwise, it is restarted in a single step using
-        <command>systemctl restart</command> in the new configuration.
-        The latter is less correct because it runs the
-        <literal>ExecStop</literal> commands from the new
-        configuration.
-      '';
-    };
-
-    startAt = mkOption {
-      type = with types; either str (listOf str);
-      default = [];
-      example = "Sun 14:00:00";
-      description = ''
-        Automatically start this unit at the given date/time, which
-        must be in the format described in
-        <citerefentry><refentrytitle>systemd.time</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry>.  This is equivalent
-        to adding a corresponding timer unit with
-        <option>OnCalendar</option> set to the value given here.
-      '';
-      apply = v: if isList v then v else [ v ];
-    };
+  }; };
+
+  stage2ServiceOptions = { name, config, ... }: {
+    imports = [
+      stage2CommonUnitOptions
+      serviceOptions
+    ];
+
+    options = {
+      script = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Shell commands executed as the service's main process.";
+      };
+
+      scriptArgs = mkOption {
+        type = types.str;
+        default = "";
+        description = "Arguments passed to the main process script.";
+      };
+
+      preStart = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Shell commands executed before the service's main process
+          is started.
+        '';
+      };
+
+      postStart = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Shell commands executed after the service's main process
+          is started.
+        '';
+      };
+
+      reload = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Shell commands executed when the service's main process
+          is reloaded.
+        '';
+      };
+
+      preStop = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Shell commands executed to stop the service.
+        '';
+      };
+
+      postStop = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Shell commands executed after the service's main process
+          has exited.
+        '';
+      };
+
+      restartIfChanged = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether the service should be restarted during a NixOS
+          configuration switch if its definition has changed.
+        '';
+      };
+
+      reloadIfChanged = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether the service should be reloaded during a NixOS
+          configuration switch if its definition has changed.  If
+          enabled, the value of <option>restartIfChanged</option> is
+          ignored.
+
+          This option should not be used anymore in favor of
+          <option>reloadTriggers</option> which allows more granular
+          control of when a service is reloaded and when a service
+          is restarted.
+        '';
+      };
+
+      stopIfChanged = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If set, a changed unit is restarted by calling
+          <command>systemctl stop</command> in the old configuration,
+          then <command>systemctl start</command> in the new one.
+          Otherwise, it is restarted in a single step using
+          <command>systemctl restart</command> in the new configuration.
+          The latter is less correct because it runs the
+          <literal>ExecStop</literal> commands from the new
+          configuration.
+        '';
+      };
+
+      startAt = mkOption {
+        type = with types; either str (listOf str);
+        default = [];
+        example = "Sun 14:00:00";
+        description = ''
+          Automatically start this unit at the given date/time, which
+          must be in the format described in
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.  This is equivalent
+          to adding a corresponding timer unit with
+          <option>OnCalendar</option> set to the value given here.
+        '';
+        apply = v: if isList v then v else [ v ];
+      };
+    };
+
+    config = mkMerge
+      [ (mkIf (config.preStart != "")
+          { serviceConfig.ExecStartPre =
+              [ (makeJobScript "${name}-pre-start" config.preStart) ];
+          })
+        (mkIf (config.script != "")
+          { serviceConfig.ExecStart =
+              makeJobScript "${name}-start" config.script + " " + config.scriptArgs;
+          })
+        (mkIf (config.postStart != "")
+          { serviceConfig.ExecStartPost =
+              [ (makeJobScript "${name}-post-start" config.postStart) ];
+          })
+        (mkIf (config.reload != "")
+          { serviceConfig.ExecReload =
+              makeJobScript "${name}-reload" config.reload;
+          })
+        (mkIf (config.preStop != "")
+          { serviceConfig.ExecStop =
+              makeJobScript "${name}-pre-stop" config.preStop;
+          })
+        (mkIf (config.postStop != "")
+          { serviceConfig.ExecStopPost =
+              makeJobScript "${name}-post-stop" config.postStop;
+          })
+      ];
+  };
 
+  stage1ServiceOptions = {
+    imports = [
+      stage1CommonUnitOptions
+      serviceOptions
+    ];
   };
 
 
-  socketOptions = commonUnitOptions // {
+  socketOptions = { options = {
 
     listenStreams = mkOption {
       default = [];
@@ -424,10 +475,24 @@ in rec {
       '';
     };
 
+  }; };
+
+  stage2SocketOptions = {
+    imports = [
+      stage2CommonUnitOptions
+      socketOptions
+    ];
   };
 
+  stage1SocketOptions = {
+    imports = [
+      stage1CommonUnitOptions
+      socketOptions
+    ];
+  };
 
-  timerOptions = commonUnitOptions // {
+
+  timerOptions = { options = {
 
     timerConfig = mkOption {
       default = {};
@@ -443,10 +508,24 @@ in rec {
       '';
     };
 
+  }; };
+
+  stage2TimerOptions = {
+    imports = [
+      stage2CommonUnitOptions
+      timerOptions
+    ];
   };
 
+  stage1TimerOptions = {
+    imports = [
+      stage1CommonUnitOptions
+      timerOptions
+    ];
+  };
 
-  pathOptions = commonUnitOptions // {
+
+  pathOptions = { options = {
 
     pathConfig = mkOption {
       default = {};
@@ -460,10 +539,24 @@ in rec {
       '';
     };
 
+  }; };
+
+  stage2PathOptions = {
+    imports = [
+      stage2CommonUnitOptions
+      pathOptions
+    ];
+  };
+
+  stage1PathOptions = {
+    imports = [
+      stage1CommonUnitOptions
+      pathOptions
+    ];
   };
 
 
-  mountOptions = commonUnitOptions // {
+  mountOptions = { options = {
 
     what = mkOption {
       example = "/dev/sda1";
@@ -505,9 +598,23 @@ in rec {
         <manvolnum>5</manvolnum></citerefentry> for details.
       '';
     };
+  }; };
+
+  stage2MountOptions = {
+    imports = [
+      stage2CommonUnitOptions
+      mountOptions
+    ];
+  };
+
+  stage1MountOptions = {
+    imports = [
+      stage1CommonUnitOptions
+      mountOptions
+    ];
   };
 
-  automountOptions = commonUnitOptions // {
+  automountOptions = { options = {
 
     where = mkOption {
       example = "/mnt";
@@ -529,11 +636,23 @@ in rec {
         <manvolnum>5</manvolnum></citerefentry> for details.
       '';
     };
+  }; };
+
+  stage2AutomountOptions = {
+    imports = [
+      stage2CommonUnitOptions
+      automountOptions
+    ];
   };
 
-  targetOptions = commonUnitOptions;
+  stage1AutomountOptions = {
+    imports = [
+      stage1CommonUnitOptions
+      automountOptions
+    ];
+  };
 
-  sliceOptions = commonUnitOptions // {
+  sliceOptions = { options = {
 
     sliceConfig = mkOption {
       default = {};
@@ -547,6 +666,20 @@ in rec {
       '';
     };
 
+  }; };
+
+  stage2SliceOptions = {
+    imports = [
+      stage2CommonUnitOptions
+      sliceOptions
+    ];
+  };
+
+  stage1SliceOptions = {
+    imports = [
+      stage1CommonUnitOptions
+      sliceOptions
+    ];
   };
 
 }
diff --git a/nixos/lib/testing-python.nix b/nixos/lib/testing-python.nix
index facc7a253a759..cd2bb2f9d4d4c 100644
--- a/nixos/lib/testing-python.nix
+++ b/nixos/lib/testing-python.nix
@@ -206,6 +206,7 @@ rec {
             )];
           };
         in
+          lib.warnIf (t?machine) "In test `${name}': The `machine' attribute in NixOS tests (pkgs.nixosTest / make-test-pyton.nix / testing-python.nix / makeTest) is deprecated. Please use the equivalent `nodes.machine'."
           build-vms.buildVirtualNetwork (
               nodes // lib.optionalAttrs (machine != null) { inherit machine; }
           );
diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix
index ae68c3920c5bb..80341dd48fcd5 100644
--- a/nixos/lib/utils.nix
+++ b/nixos/lib/utils.nix
@@ -197,5 +197,6 @@ rec {
   systemdUtils = {
     lib = import ./systemd-lib.nix { inherit lib config pkgs; };
     unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils; };
+    types = import ./systemd-types.nix { inherit lib systemdUtils; };
   };
 }
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index d825f4beb301c..931201ade2935 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -15,6 +15,26 @@ let
       mapAttrsToList (n: v: ''${n}=${escapeIfNeccessary (toString v)}'') attrs
     );
 
+  osReleaseContents = {
+    NAME = "NixOS";
+    ID = "nixos";
+    VERSION = "${cfg.release} (${cfg.codeName})";
+    VERSION_CODENAME = toLower cfg.codeName;
+    VERSION_ID = cfg.release;
+    BUILD_ID = cfg.version;
+    PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
+    LOGO = "nix-snowflake";
+    HOME_URL = "https://nixos.org/";
+    DOCUMENTATION_URL = "https://nixos.org/learn.html";
+    SUPPORT_URL = "https://nixos.org/community.html";
+    BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
+  };
+
+  initrdReleaseContents = osReleaseContents // {
+    PRETTY_NAME = "${osReleaseContents.PRETTY_NAME} (Initrd)";
+  };
+  initrdRelease = pkgs.writeText "initrd-release" (attrsToText initrdReleaseContents);
+
 in
 {
   imports = [
@@ -119,20 +139,12 @@ in
         DISTRIB_DESCRIPTION = "NixOS ${cfg.release} (${cfg.codeName})";
       };
 
-      "os-release".text = attrsToText {
-        NAME = "NixOS";
-        ID = "nixos";
-        VERSION = "${cfg.release} (${cfg.codeName})";
-        VERSION_CODENAME = toLower cfg.codeName;
-        VERSION_ID = cfg.release;
-        BUILD_ID = cfg.version;
-        PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
-        LOGO = "nix-snowflake";
-        HOME_URL = "https://nixos.org/";
-        DOCUMENTATION_URL = "https://nixos.org/learn.html";
-        SUPPORT_URL = "https://nixos.org/community.html";
-        BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
-      };
+      "os-release".text = attrsToText osReleaseContents;
+    };
+
+    boot.initrd.systemd.contents = {
+      "/etc/os-release".source = initrdRelease;
+      "/etc/initrd-release".source = initrdRelease;
     };
   };
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 71c84fbe6b4b3..789faea91977c 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -118,6 +118,7 @@
   ./misc/version.nix
   ./misc/wordlist.nix
   ./misc/nixops-autoluks.nix
+  ./programs/_1password.nix
   ./programs/_1password-gui.nix
   ./programs/adb.nix
   ./programs/appgate-sdp.nix
@@ -976,6 +977,7 @@
   ./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/tor.nix
   ./services/security/torify.nix
@@ -1047,6 +1049,7 @@
   ./services/web-apps/mediawiki.nix
   ./services/web-apps/miniflux.nix
   ./services/web-apps/moodle.nix
+  ./services/web-apps/netbox.nix
   ./services/web-apps/nextcloud.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/node-red.nix
@@ -1178,6 +1181,7 @@
   ./system/boot/systemd/nspawn.nix
   ./system/boot/systemd/tmpfiles.nix
   ./system/boot/systemd/user.nix
+  ./system/boot/systemd/initrd.nix
   ./system/boot/timesyncd.nix
   ./system/boot/tmp.nix
   ./system/etc/etc-activation.nix
diff --git a/nixos/modules/programs/_1password-gui.nix b/nixos/modules/programs/_1password-gui.nix
index f57de44bb9e26..42f6a0b52252c 100644
--- a/nixos/modules/programs/_1password-gui.nix
+++ b/nixos/modules/programs/_1password-gui.nix
@@ -3,67 +3,66 @@
 with lib;
 
 let
+
   cfg = config.programs._1password-gui;
 
-in {
+in
+{
   options = {
     programs._1password-gui = {
-      enable = mkEnableOption "The 1Password Desktop application with browser integration";
+      enable = mkEnableOption "the 1Password GUI application";
 
-      groupId = mkOption {
-        type = types.int;
+      gid = mkOption {
+        type = types.addCheck types.int (x: x >= 1000);
         example = literalExpression "5000";
         description = ''
-          The GroupID to assign to the onepassword group, which is needed for browser integration. The group ID must be 1000 or greater.
-          '';
+          The gid to assign to the onepassword group, which is needed for browser integration.
+          It must be 1000 or greater.
+        '';
       };
 
       polkitPolicyOwners = mkOption {
         type = types.listOf types.str;
-        default = [];
-        example = literalExpression "[\"user1\" \"user2\" \"user3\"]";
+        default = [ ];
+        example = literalExpression ''["user1" "user2" "user3"]'';
         description = ''
-          A list of users who should be able to integrate 1Password with polkit-based authentication mechanisms. By default, no users will have such access.
-          '';
+          A list of users who should be able to integrate 1Password with polkit-based authentication mechanisms.
+        '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs._1password-gui;
-        defaultText = literalExpression "pkgs._1password-gui";
-        example = literalExpression "pkgs._1password-gui";
-        description = ''
-          The 1Password derivation to use. This can be used to upgrade from the stable release that we keep in nixpkgs to the betas.
-          '';
+      package = mkPackageOption pkgs "1Password GUI" {
+        default = [ "_1password-gui" ];
       };
     };
   };
 
-  config = let
-    package = cfg.package.override {
-      polkitPolicyOwners = cfg.polkitPolicyOwners;
-    };
-  in mkIf cfg.enable {
-    environment.systemPackages = [ package ];
-    users.groups.onepassword.gid = cfg.groupId;
+  config =
+    let
+      package = cfg.package.override {
+        polkitPolicyOwners = cfg.polkitPolicyOwners;
+      };
+    in
+    mkIf cfg.enable {
+      environment.systemPackages = [ package ];
+      users.groups.onepassword.gid = cfg.gid;
 
-    security.wrappers = {
-      "1Password-BrowserSupport" =
-        { source = "${cfg.package}/share/1password/1Password-BrowserSupport";
+      security.wrappers = {
+        "1Password-BrowserSupport" = {
+          source = "${package}/share/1password/1Password-BrowserSupport";
           owner = "root";
           group = "onepassword";
           setuid = false;
           setgid = true;
         };
 
-      "1Password-KeyringHelper" =
-        { source = "${cfg.package}/share/1password/1Password-KeyringHelper";
+        "1Password-KeyringHelper" = {
+          source = "${package}/share/1password/1Password-KeyringHelper";
           owner = "root";
           group = "onepassword";
           setuid = true;
           setgid = true;
         };
-    };
+      };
 
-  };
+    };
 }
diff --git a/nixos/modules/programs/_1password.nix b/nixos/modules/programs/_1password.nix
new file mode 100644
index 0000000000000..547c12867a916
--- /dev/null
+++ b/nixos/modules/programs/_1password.nix
@@ -0,0 +1,44 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs._1password;
+
+in
+{
+  options = {
+    programs._1password = {
+      enable = mkEnableOption "the 1Password CLI tool";
+
+      gid = mkOption {
+        type = types.addCheck types.int (x: x >= 1000);
+        example = literalExpression "5001";
+        description = ''
+          The gid to assign to the onepassword-cli group, which is needed for integration with the 1Password GUI.
+          It must be 1000 or greater.
+        '';
+      };
+
+      package = mkPackageOption pkgs "1Password CLI" {
+        default = [ "_1password" ];
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    users.groups.onepassword-cli.gid = cfg.gid;
+
+    security.wrappers = {
+      "op" = {
+        source = "${cfg.package}/bin/op";
+        owner = "root";
+        group = "onepassword-cli";
+        setuid = false;
+        setgid = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/backup/zrepl.nix b/nixos/modules/services/backup/zrepl.nix
index 4356479b6635e..73f5e4d9f6d76 100644
--- a/nixos/modules/services/backup/zrepl.nix
+++ b/nixos/modules/services/backup/zrepl.nix
@@ -38,6 +38,9 @@ in
     environment.etc."zrepl/zrepl.yml".source = configFile;
 
     systemd.packages = [ pkgs.zrepl ];
+
+    # Note that pkgs.zrepl copies and adapts the upstream systemd unit, and
+    # the fields defined here only override certain fields from that unit.
     systemd.services.zrepl = {
       requires = [ "local-fs.target" ];
       wantedBy = [ "zfs.target" ];
diff --git a/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixos/modules/services/continuous-integration/jenkins/slave.nix
index 3c0e6f78e74ca..871b9914fb27a 100644
--- a/nixos/modules/services/continuous-integration/jenkins/slave.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/slave.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 with lib;
 let
   cfg = config.services.jenkinsSlave;
@@ -46,6 +46,15 @@ in {
           this is the home of the "jenkins" user.
         '';
       };
+
+      javaPackage = mkOption {
+        default = pkgs.jdk;
+        defaultText = literalExpression "pkgs.jdk";
+        description = ''
+          Java package to install.
+        '';
+        type = types.package;
+      };
     };
   };
 
@@ -64,5 +73,10 @@ in {
         uid = config.ids.uids.jenkins;
       };
     };
+
+    programs.java = {
+      enable = true;
+      package = cfg.javaPackage;
+    };
   };
 }
diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
index df0f62556dff6..b19fb33ec1788 100644
--- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
+++ b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json
@@ -29,14 +29,7 @@
     },
     {
       "name": "libpipewire-module-protocol-pulse",
-      "args": {
-        "server.address": [
-          "unix:native"
-        ],
-        "vm.overrides": {
-          "pulse.min.quantum": "1024/48000"
-        }
-      }
+      "args": {}
     }
   ],
   "context.exec": [
@@ -46,6 +39,14 @@
     }
   ],
   "stream.properties": {},
+  "pulse.properties": {
+    "server.address": [
+      "unix:native"
+    ],
+    "vm.overrides": {
+      "pulse.min.quantum": "1024/48000"
+    }
+  },
   "pulse.rules": [
     {
       "matches": [
diff --git a/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixos/modules/services/desktops/pipewire/wireplumber.nix
index 32206ccb4e600..1dbdd842c4a1e 100644
--- a/nixos/modules/services/desktops/pipewire/wireplumber.nix
+++ b/nixos/modules/services/desktops/pipewire/wireplumber.nix
@@ -38,7 +38,7 @@ in
 
     environment.etc."wireplumber/main.lua.d/80-nixos.lua" = lib.mkIf (!pwUsedForAudio) {
      text = ''
-        # Pipewire is not used for audio, so prevent it from grabbing audio devices
+        -- Pipewire is not used for audio, so prevent it from grabbing audio devices
         alsa_monitor.enable = function() end
       '';
     };
diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix
index 96fcd6d2c8b30..ff73d7a46ed39 100644
--- a/nixos/modules/services/games/factorio.nix
+++ b/nixos/modules/services/games/factorio.nix
@@ -53,6 +53,14 @@ in
         '';
       };
 
+      bind = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = ''
+          The address to which the service should bind.
+        '';
+      };
+
       admins = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -241,6 +249,7 @@ in
           "${cfg.package}/bin/factorio"
           "--config=${cfg.configFile}"
           "--port=${toString cfg.port}"
+          "--bind=${cfg.bind}"
           "--start-server=${mkSavePath cfg.saveName}"
           "--server-settings=${serverSettingsFile}"
           (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
diff --git a/nixos/modules/services/hardware/joycond.nix b/nixos/modules/services/hardware/joycond.nix
index ffef4f8a4e188..d81c1bb6d63d0 100644
--- a/nixos/modules/services/hardware/joycond.nix
+++ b/nixos/modules/services/hardware/joycond.nix
@@ -22,13 +22,9 @@ with lib;
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [
-      kernelPackages.hid-nintendo
-      cfg.package
-    ];
+    environment.systemPackages = [ cfg.package ];
 
-    boot.extraModulePackages = [ kernelPackages.hid-nintendo ];
-    boot.kernelModules = [ "hid_nintendo" ];
+    boot.extraModulePackages = optional (versionOlder kernelPackages.kernel.version "5.16") kernelPackages.hid-nintendo;
 
     services.udev.packages = [ cfg.package ];
 
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index 082cf92ff4efe..332a2a597edc1 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -5,7 +5,10 @@ with lib;
 let
   cfg = config.services.logrotate;
 
-  pathOpts = { name, ... }:  {
+  # deprecated legacy compat settings
+  # these options will be removed before 22.11 in the following PR:
+  # https://github.com/NixOS/nixpkgs/pull/164169
+  pathOpts = { name, ... }: {
     options = {
       enable = mkOption {
         type = types.bool;
@@ -86,23 +89,113 @@ let
     config.name = name;
   };
 
-  mkConf = pathOpts: ''
-    # generated by NixOS using the `services.logrotate.paths.${pathOpts.name}` attribute set
-    ${concatMapStringsSep " " (path: ''"${path}"'') (toList pathOpts.path)} {
-      ${optionalString (pathOpts.user != null || pathOpts.group != null) "su ${pathOpts.user} ${pathOpts.group}"}
-      ${pathOpts.frequency}
-      rotate ${toString pathOpts.keep}
-      ${pathOpts.extraConfig}
-    }
-  '';
-
-  paths = sortProperties (attrValues (filterAttrs (_: pathOpts: pathOpts.enable) cfg.paths));
-  configFile = pkgs.writeText "logrotate.conf" (
-    concatStringsSep "\n" (
-      [ "missingok" "notifempty" cfg.extraConfig ] ++ (map mkConf paths)
-    )
+  generateLine = n: v:
+    if builtins.elem n [ "files" "priority" "enable" "global" ] || v == null then null
+    else if builtins.elem n [ "extraConfig" "frequency" ] then "${v}\n"
+    else if builtins.elem n [ "firstaction" "lastaction" "prerotate" "postrotate" "preremove" ]
+         then "${n}\n    ${v}\n  endscript\n"
+    else if isInt v then "${n} ${toString v}\n"
+    else if v == true then "${n}\n"
+    else if v == false then "no${n}\n"
+    else "${n} ${v}\n";
+  generateSection = indent: settings: concatStringsSep (fixedWidthString indent " " "") (
+    filter (x: x != null) (mapAttrsToList generateLine settings)
   );
 
+  # generateSection includes a final newline hence weird closing brace
+  mkConf = settings:
+    if settings.global or false then generateSection 0 settings
+    else ''
+      ${concatMapStringsSep "\n" (files: ''"${files}"'') (toList settings.files)} {
+        ${generateSection 2 settings}}
+    '';
+
+  # below two mapPaths are compat functions
+  mapPathOptToSetting = n: v:
+    if n == "keep" then nameValuePair "rotate" v
+    else if n == "path" then nameValuePair "files" v
+    else nameValuePair n v;
+
+  mapPathsToSettings = path: pathOpts:
+    nameValuePair path (
+      filterAttrs (n: v: ! builtins.elem n [ "user" "group" "name" ] && v != "") (
+        (mapAttrs' mapPathOptToSetting pathOpts) //
+        {
+          su =
+            if pathOpts.user != null
+            then "${pathOpts.user} ${pathOpts.group}"
+            else null;
+        }
+      )
+    );
+
+  settings = sortProperties (attrValues (filterAttrs (_: settings: settings.enable) (
+    foldAttrs recursiveUpdate { } [
+      {
+        header = {
+          enable = true;
+          missingok = true;
+          notifempty = true;
+          frequency = "weekly";
+          rotate = 4;
+        };
+        # compat section
+        extraConfig = {
+          enable = (cfg.extraConfig != "");
+          global = true;
+          extraConfig = cfg.extraConfig;
+          priority = 101;
+        };
+      }
+      (mapAttrs' mapPathsToSettings cfg.paths)
+      cfg.settings
+      { header = { global = true; priority = 100; }; }
+    ]
+  )));
+  configFile = pkgs.writeTextFile {
+    name = "logrotate.conf";
+    text = concatStringsSep "\n" (
+      map mkConf settings
+    );
+    checkPhase = optionalString cfg.checkConfig ''
+      # logrotate --debug also checks that users specified in config
+      # file exist, but we only have sandboxed users here so brown these
+      # out. according to man page that means su, create and createolddir.
+      # files required to exist also won't be present, so missingok is forced.
+      user=$(${pkgs.coreutils}/bin/id -un)
+      group=$(${pkgs.coreutils}/bin/id -gn)
+      sed -e "s/\bsu\s.*/su $user $group/" \
+          -e "s/\b\(create\s\+[0-9]*\s*\|createolddir\s\+[0-9]*\s\+\).*/\1$user $group/" \
+          -e "1imissingok" -e "s/\bnomissingok\b//" \
+          $out > /tmp/logrotate.conf
+      # Since this makes for very verbose builds only show real error.
+      # There is no way to control log level, but logrotate hardcodes
+      # 'error:' at common log level, so we can use grep, taking care
+      # to keep error codes
+      set -o pipefail
+      if ! ${pkgs.logrotate}/sbin/logrotate --debug /tmp/logrotate.conf 2>&1 \
+          | ( ! grep "error:" ) > /tmp/logrotate-error; then
+              echo "Logrotate configuration check failed."
+              echo "The failing configuration (after adjustments to pass tests in sandbox) was:"
+              printf "%s\n" "-------"
+              cat /tmp/logrotate.conf
+              printf "%s\n" "-------"
+              echo "The error reported by logrotate was as follow:"
+              printf "%s\n" "-------"
+              cat /tmp/logrotate-error
+              printf "%s\n" "-------"
+              echo "You can disable this check with services.logrotate.checkConfig = false,"
+              echo "but if you think it should work please report this failure along with"
+              echo "the config file being tested!"
+              false
+      fi
+    '';
+  };
+
+  mailOption =
+    if foldr (n: a: a || n ? mail) false (attrValues cfg.settings)
+    then "--mail=${pkgs.mailutils}/bin/mail"
+    else "";
 in
 {
   imports = [
@@ -112,17 +205,121 @@ in
   options = {
     services.logrotate = {
       enable = mkEnableOption "the logrotate systemd service" // {
-        default = foldr (n: a: a || n.enable) false (attrValues cfg.paths);
-        defaultText = literalExpression "cfg.paths != {}";
+        default = foldr (n: a: a || n.enable) false (attrValues cfg.settings);
+        defaultText = literalExpression "cfg.settings != {}";
+      };
+
+      settings = mkOption {
+        default = { };
+        description = ''
+          logrotate freeform settings: each attribute here will define its own section,
+          ordered by priority, which can either define files to rotate with their settings
+          or settings common to all further files settings.
+          Refer to <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+        '';
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          freeformType = with types; attrsOf (nullOr (oneOf [ int bool str ]));
+
+          options = {
+            enable = mkEnableOption "setting individual kill switch" // {
+              default = true;
+            };
+
+            global = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether this setting is a global option or not: set to have these
+                settings apply to all files settings with a higher priority.
+              '';
+            };
+            files = mkOption {
+              type = with types; either str (listOf str);
+              default = name;
+              defaultText = ''
+                The attrset name if not specified
+              '';
+              description = ''
+                Single or list of files for which rules are defined.
+                The files are quoted with double-quotes in logrotate configuration,
+                so globs and spaces are supported.
+                Note this setting is ignored if globals is true.
+              '';
+            };
+
+            frequency = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                How often to rotate the logs. Defaults to previously set global setting,
+                which itself defauts to weekly.
+              '';
+            };
+
+            priority = mkOption {
+              type = types.int;
+              default = 1000;
+              description = ''
+                Order of this logrotate block in relation to the others. The semantics are
+                the same as with `lib.mkOrder`. Smaller values are inserted first.
+              '';
+            };
+          };
+
+        }));
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        default = configFile;
+        defaultText = ''
+          A configuration file automatically generated by NixOS.
+        '';
+        description = ''
+          Override the configuration file used by MySQL. By default,
+          NixOS generates one automatically from <xref linkend="opt-services.logrotate.settings"/>.
+        '';
+        example = literalExpression ''
+          pkgs.writeText "logrotate.conf" '''
+            missingok
+            "/var/log/*.log" {
+              rotate 4
+              weekly
+            }
+          ''';
+        '';
       };
 
+      checkConfig = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether the config should be checked at build time.
+
+          Some options are not checkable at build time because of the build sandbox:
+          for example, the test does not know about existing files and system users are
+          not known.
+          These limitations mean we must adjust the file for tests (missingok is forced
+          and users are replaced by dummy users), so tests are complemented by a
+          logrotate-checkconf service that is enabled by default.
+          This extra check can be disabled by disabling it at the systemd level with the
+          <option>services.systemd.services.logrotate-checkconf.enable</option> option.
+
+          Conversely there are still things that might make this check fail incorrectly
+          (e.g. a file path where we don't have access to intermediate directories):
+          in this case you can disable the failing check with this option.
+        '';
+      };
+
+      # deprecated legacy compat settings
       paths = mkOption {
         type = with types; attrsOf (submodule pathOpts);
-        default = {};
+        default = { };
         description = ''
           Attribute set of paths to rotate. The order each block appears in the generated configuration file
           can be controlled by the <link linkend="opt-services.logrotate.paths._name_.priority">priority</link> option
           using the same semantics as `lib.mkOrder`. Smaller values have a greater priority.
+          This setting has been deprecated in favor of <link linkend="opt-services.logrotate.settings">logrotate settings</link>.
         '';
         example = literalExpression ''
           {
@@ -151,19 +348,37 @@ in
         description = ''
           Extra contents to append to the logrotate configuration file. Refer to
           <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
+          This setting has been deprecated in favor of
+          <link linkend="opt-services.logrotate.settings">logrotate settings</link>.
         '';
       };
     };
   };
 
   config = mkIf cfg.enable {
-    assertions = mapAttrsToList (name: pathOpts:
-      { assertion = (pathOpts.user != null) == (pathOpts.group != null);
-        message = ''
-          If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
-        '';
-      }
-    ) cfg.paths;
+    assertions =
+      mapAttrsToList
+        (name: pathOpts:
+          {
+            assertion = (pathOpts.user != null) == (pathOpts.group != null);
+            message = ''
+              If either of `services.logrotate.paths.${name}.user` or `services.logrotate.paths.${name}.group` are specified then *both* must be specified.
+            '';
+          })
+        cfg.paths;
+
+    warnings =
+      (mapAttrsToList
+        (name: pathOpts: ''
+          Using config.services.logrotate.paths.${name} is deprecated and will become unsupported in a future release.
+          Please use services.logrotate.settings instead.
+        '')
+        cfg.paths
+      ) ++
+      (optional (cfg.extraConfig != "") ''
+        Using config.services.logrotate.extraConfig is deprecated and will become unsupported in a future release.
+        Please use services.logrotate.settings with globals=true instead.
+      '');
 
     systemd.services.logrotate = {
       description = "Logrotate Service";
@@ -172,7 +387,16 @@ in
       serviceConfig = {
         Restart = "no";
         User = "root";
-        ExecStart = "${pkgs.logrotate}/sbin/logrotate ${configFile}";
+        ExecStart = "${pkgs.logrotate}/sbin/logrotate ${mailOption} ${cfg.configFile}";
+      };
+    };
+    systemd.services.logrotate-checkconf = {
+      description = "Logrotate configuration check";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${pkgs.logrotate}/sbin/logrotate --debug ${cfg.configFile}";
       };
     };
   };
diff --git a/nixos/modules/services/matrix/matrix-synapse.nix b/nixos/modules/services/matrix/matrix-synapse.nix
index c4d14dbd547ef..4abcc8b69bc53 100644
--- a/nixos/modules/services/matrix/matrix-synapse.nix
+++ b/nixos/modules/services/matrix/matrix-synapse.nix
@@ -141,7 +141,7 @@ in {
       enable = mkEnableOption "matrix.org synapse";
 
       configFile = mkOption {
-        type = types.str;
+        type = types.path;
         readOnly = true;
         description = ''
           Path to the configuration file on the target system. Useful to configure e.g. workers
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index e48444f716123..488c3be7b653a 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -848,10 +848,7 @@ in {
 
         extraConfig = mkOption {
           type = types.lines;
-          default = ''
-            copytruncate
-            compress
-          '';
+          default = "";
           description = ''
             Extra logrotate config options for this path. Refer to
             <link xlink:href="https://linux.die.net/man/8/logrotate"/> for details.
@@ -977,13 +974,14 @@ in {
     # Enable rotation of log files
     services.logrotate = {
       enable = cfg.logrotate.enable;
-      paths = {
+      settings = {
         gitlab = {
-          path = "${cfg.statePath}/log/*.log";
-          user = cfg.user;
-          group = cfg.group;
+          files = "${cfg.statePath}/log/*.log";
+          su = "${cfg.user} ${cfg.group}";
           frequency = cfg.logrotate.frequency;
-          keep = cfg.logrotate.keep;
+          rotate = cfg.logrotate.keep;
+          copytruncate = true;
+          compress = true;
           extraConfig = cfg.logrotate.extraConfig;
         };
       };
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index d56808c7564ea..4bc5b04d3a08b 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -409,14 +409,14 @@ in
               to = mkOption {
                 type = referenceAttrs;
                 example = { type = "github"; owner = "my-org"; repo = "my-nixpkgs"; };
-                description = "The flake reference <option>from></option> is rewritten to.";
+                description = "The flake reference <option>from</option> is rewritten to.";
               };
               flake = mkOption {
                 type = types.nullOr types.attrs;
                 default = null;
                 example = literalExpression "nixpkgs";
                 description = ''
-                  The flake input <option>from></option> is rewritten to.
+                  The flake input <option>from</option> is rewritten to.
                 '';
               };
               exact = mkOption {
diff --git a/nixos/modules/services/misc/nix-gc.nix b/nixos/modules/services/misc/nix-gc.nix
index b4b4b55a6c823..0fcb016010175 100644
--- a/nixos/modules/services/misc/nix-gc.nix
+++ b/nixos/modules/services/misc/nix-gc.nix
@@ -39,7 +39,7 @@ in
         type = types.str;
         example = "45min";
         description = ''
-          Add a randomized delay before each automatic upgrade.
+          Add a randomized delay before each garbage collection.
           The delay will be chosen between zero and this value.
           This value must be a time span in the format specified by
           <citerefentry><refentrytitle>systemd.time</refentrytitle>
diff --git a/nixos/modules/services/misc/paperless-ng.nix b/nixos/modules/services/misc/paperless-ng.nix
index 11e44f5ece575..881fa93c04eed 100644
--- a/nixos/modules/services/misc/paperless-ng.nix
+++ b/nixos/modules/services/misc/paperless-ng.nix
@@ -216,6 +216,8 @@ in
         Restart = "on-failure";
         # The `mbind` syscall is needed for running the classifier.
         SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "mbind" ];
+        # Needs to talk to mail server for automated import rules
+        PrivateNetwork = false;
       };
       environment = env;
       wantedBy = [ "multi-user.target" ];
@@ -258,8 +260,6 @@ in
             '${cfg.passwordFile}' '${cfg.dataDir}/superuser-password'
         '';
         Type = "oneshot";
-        # Needs to talk to mail server for automated import rules
-        PrivateNetwork = false;
       };
     };
 
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index 8d81737a3ef0d..1b9af58575604 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -5,36 +5,15 @@ with lib;
 let
   cfg = config.services.collectd;
 
-  unvalidated_conf = pkgs.writeText "collectd-unvalidated.conf" ''
-    BaseDir "${cfg.dataDir}"
-    AutoLoadPlugin ${boolToString cfg.autoLoadPlugin}
-    Hostname "${config.networking.hostName}"
-
-    LoadPlugin syslog
-    <Plugin "syslog">
-      LogLevel "info"
-      NotifyLevel "OKAY"
-    </Plugin>
-
-    ${concatStrings (mapAttrsToList (plugin: pluginConfig: ''
-      LoadPlugin ${plugin}
-      <Plugin "${plugin}">
-      ${pluginConfig}
-      </Plugin>
-    '') cfg.plugins)}
-
-    ${concatMapStrings (f: ''
-      Include "${f}"
-    '') cfg.include}
-
-    ${cfg.extraConfig}
-  '';
+  baseDirLine = ''BaseDir "${cfg.dataDir}"'';
+  unvalidated_conf = pkgs.writeText "collectd-unvalidated.conf" cfg.extraConfig;
 
   conf = if cfg.validateConfig then
     pkgs.runCommand "collectd.conf" {} ''
       echo testing ${unvalidated_conf}
+      cp ${unvalidated_conf} collectd.conf
       # collectd -t fails if BaseDir does not exist.
-      sed '1s/^BaseDir.*$/BaseDir "."/' ${unvalidated_conf} > collectd.conf
+      substituteInPlace collectd.conf --replace ${lib.escapeShellArgs [ baseDirLine ]} 'BaseDir "."'
       ${package}/bin/collectd -t -C collectd.conf
       cp ${unvalidated_conf} $out
     '' else unvalidated_conf;
@@ -123,7 +102,8 @@ in {
     extraConfig = mkOption {
       default = "";
       description = ''
-        Extra configuration for collectd.
+        Extra configuration for collectd. Use mkBefore to add lines before the
+        default config, and mkAfter to add them below.
       '';
       type = lines;
     };
@@ -131,6 +111,30 @@ in {
   };
 
   config = mkIf cfg.enable {
+    # 1200 is after the default (1000) but before mkAfter (1500).
+    services.collectd.extraConfig = lib.mkOrder 1200 ''
+      ${baseDirLine}
+      AutoLoadPlugin ${boolToString cfg.autoLoadPlugin}
+      Hostname "${config.networking.hostName}"
+
+      LoadPlugin syslog
+      <Plugin "syslog">
+        LogLevel "info"
+        NotifyLevel "OKAY"
+      </Plugin>
+
+      ${concatStrings (mapAttrsToList (plugin: pluginConfig: ''
+        LoadPlugin ${plugin}
+        <Plugin "${plugin}">
+        ${pluginConfig}
+        </Plugin>
+      '') cfg.plugins)}
+
+      ${concatMapStrings (f: ''
+        Include "${f}"
+      '') cfg.include}
+    '';
+
     systemd.tmpfiles.rules = [
       "d '${cfg.dataDir}' - ${cfg.user} - - -"
     ];
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 81fca33f5fec4..b959379d331a7 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -214,6 +214,11 @@ let
           type = types.path;
           description = "Path grafana will watch for dashboards.";
         };
+        foldersFromFilesStructure = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Use folder names from filesystem to create folders in Grafana.";
+        };
       };
     };
   };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
index 1ef264fc86e5a..5fda4c980ebb8 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bird.nix
@@ -17,7 +17,7 @@ in
     };
     birdSocket = mkOption {
       type = types.path;
-      default = "/var/run/bird.ctl";
+      default = "/run/bird/bird.ctl";
       description = ''
         Path to BIRD2 (or BIRD1 v4) socket.
       '';
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
index 27aeb9096243c..e0ee90d9b97db 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
@@ -25,6 +25,10 @@ in {
     };
   };
   serviceOpts = {
+    after = [
+      "kea-dhcp4-server.service"
+      "kea-dhcp6-server.service"
+    ];
     serviceConfig = {
       User = "kea";
       ExecStart = ''
diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix
index 3c4c0069dfd00..49950efc0a1bd 100644
--- a/nixos/modules/services/networking/dhcpd.nix
+++ b/nixos/modules/services/networking/dhcpd.nix
@@ -7,7 +7,7 @@ let
   cfg4 = config.services.dhcpd4;
   cfg6 = config.services.dhcpd6;
 
-  writeConfig = cfg: pkgs.writeText "dhcpd.conf"
+  writeConfig = postfix: cfg: pkgs.writeText "dhcpd.conf"
     ''
       default-lease-time 600;
       max-lease-time 7200;
@@ -21,7 +21,9 @@ let
           (machine: ''
             host ${machine.hostName} {
               hardware ethernet ${machine.ethernetAddress};
-              fixed-address ${machine.ipAddress};
+              fixed-address${
+                optionalString (postfix == "6") postfix
+              } ${machine.ipAddress};
             }
           '')
           cfg.machines
@@ -33,7 +35,7 @@ let
       configFile =
         if cfg.configFile != null
           then cfg.configFile
-          else writeConfig cfg;
+          else writeConfig postfix cfg;
       leaseFile = "/var/lib/dhcpd${postfix}/dhcpd.leases";
       args = [
         "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix
index 17b4eb2e283be..994c511bdc2d6 100644
--- a/nixos/modules/services/networking/kea.nix
+++ b/nixos/modules/services/networking/kea.nix
@@ -9,20 +9,26 @@ with lib;
 let
   cfg = config.services.kea;
 
+  xor = x: y: (!x && y) || (x && !y);
   format = pkgs.formats.json {};
 
-  ctrlAgentConfig = format.generate "kea-ctrl-agent.conf" {
+  chooseNotNull = x: y: if x != null then x else y;
+
+  ctrlAgentConfig = chooseNotNull cfg.ctrl-agent.configFile (format.generate "kea-ctrl-agent.conf" {
     Control-agent = cfg.ctrl-agent.settings;
-  };
-  dhcp4Config = format.generate "kea-dhcp4.conf" {
+  });
+
+  dhcp4Config = chooseNotNull cfg.dhcp4.configFile (format.generate "kea-dhcp4.conf" {
     Dhcp4 = cfg.dhcp4.settings;
-  };
-  dhcp6Config = format.generate "kea-dhcp6.conf" {
+  });
+
+  dhcp6Config = chooseNotNull cfg.dhcp6.configFile (format.generate "kea-dhcp6.conf" {
     Dhcp6 = cfg.dhcp6.settings;
-  };
-  dhcpDdnsConfig = format.generate "kea-dhcp-ddns.conf" {
+  });
+
+  dhcpDdnsConfig = chooseNotNull cfg.dhcp-ddns.configFile (format.generate "kea-dhcp-ddns.conf" {
     DhcpDdns = cfg.dhcp-ddns.settings;
-  };
+  });
 
   package = pkgs.kea;
 in
@@ -45,6 +51,17 @@ in
             '';
           };
 
+          configFile = mkOption {
+            type = nullOr path;
+            default = null;
+            description = ''
+              Kea Control Agent configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html"/>.
+
+              Takes preference over <link linkend="opt-services.kea.ctrl-agent.settings">settings</link>.
+              Most users should prefer using <link linkend="opt-services.kea.ctrl-agent.settings">settings</link> instead.
+            '';
+          };
+
           settings = mkOption {
             type = format.type;
             default = null;
@@ -73,6 +90,17 @@ in
             '';
           };
 
+          configFile = mkOption {
+            type = nullOr path;
+            default = null;
+            description = ''
+              Kea DHCP4 configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp4-srv.html"/>.
+
+              Takes preference over <link linkend="opt-services.kea.dhcp4.settings">settings</link>.
+              Most users should prefer using <link linkend="opt-services.kea.dhcp4.settings">settings</link> instead.
+            '';
+          };
+
           settings = mkOption {
             type = format.type;
             default = null;
@@ -122,6 +150,17 @@ in
             '';
           };
 
+          configFile = mkOption {
+            type = nullOr path;
+            default = null;
+            description = ''
+              Kea DHCP6 configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/dhcp6-srv.html"/>.
+
+              Takes preference over <link linkend="opt-services.kea.dhcp6.settings">settings</link>.
+              Most users should prefer using <link linkend="opt-services.kea.dhcp6.settings">settings</link> instead.
+            '';
+          };
+
           settings = mkOption {
             type = format.type;
             default = null;
@@ -172,6 +211,17 @@ in
             '';
           };
 
+          configFile = mkOption {
+            type = nullOr path;
+            default = null;
+            description = ''
+              Kea DHCP-DDNS configuration as a path, see <link xlink:href="https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"/>.
+
+              Takes preference over <link linkend="opt-services.kea.dhcp-ddns.settings">settings</link>.
+              Most users should prefer using <link linkend="opt-services.kea.dhcp-ddns.settings">settings</link> instead.
+            '';
+          };
+
           settings = mkOption {
             type = format.type;
             default = null;
@@ -214,6 +264,10 @@ in
   }
 
   (mkIf cfg.ctrl-agent.enable {
+    assertions = [{
+        assertion = xor (cfg.ctrl-agent.settings == null) (cfg.ctrl-agent.configFile == null);
+        message = "Either services.kea.ctrl-agent.settings or services.kea.ctrl-agent.configFile must be set to a non-null value.";
+    }];
 
     environment.etc."kea/ctrl-agent.conf".source = ctrlAgentConfig;
 
@@ -252,6 +306,10 @@ in
   })
 
   (mkIf cfg.dhcp4.enable {
+    assertions = [{
+        assertion = xor (cfg.dhcp4.settings == null) (cfg.dhcp4.configFile == null);
+        message = "Either services.kea.dhcp4.settings or services.kea.dhcp4.configFile must be set to a non-null value.";
+    }];
 
     environment.etc."kea/dhcp4-server.conf".source = dhcp4Config;
 
@@ -295,6 +353,10 @@ in
   })
 
   (mkIf cfg.dhcp6.enable {
+    assertions = [{
+        assertion = xor (cfg.dhcp6.settings == null) (cfg.dhcp6.configFile == null);
+        message = "Either services.kea.dhcp6.settings or services.kea.dhcp6.configFile must be set to a non-null value.";
+    }];
 
     environment.etc."kea/dhcp6-server.conf".source = dhcp6Config;
 
@@ -336,6 +398,10 @@ in
   })
 
   (mkIf cfg.dhcp-ddns.enable {
+    assertions = [{
+        assertion = xor (cfg.dhcp-ddns.settings == null) (cfg.dhcp-ddns.configFile == null);
+        message = "Either services.kea.dhcp-ddns.settings or services.kea.dhcp-ddns.configFile must be set to a non-null value.";
+    }];
 
     environment.etc."kea/dhcp-ddns.conf".source = dhcpDdnsConfig;
 
diff --git a/nixos/modules/services/networking/lxd-image-server.nix b/nixos/modules/services/networking/lxd-image-server.nix
index b119ba8acf634..d326626eed442 100644
--- a/nixos/modules/services/networking/lxd-image-server.nix
+++ b/nixos/modules/services/networking/lxd-image-server.nix
@@ -51,18 +51,14 @@ in
 
       environment.etc."lxd-image-server/config.toml".source = format.generate "config.toml" cfg.settings;
 
-      services.logrotate.paths.lxd-image-server = {
-        path = "/var/log/lxd-image-server/lxd-image-server.log";
+      services.logrotate.settings.lxd-image-server = {
+        files = "/var/log/lxd-image-server/lxd-image-server.log";
         frequency = "daily";
-        keep = 21;
-        extraConfig = ''
-          create 755 lxd-image-server ${cfg.group}
-          missingok
-          compress
-          delaycompress
-          copytruncate
-          notifempty
-        '';
+        rotate = 21;
+        create = "755 lxd-image-server ${cfg.group}";
+        compress = true;
+        delaycompress = true;
+        copytruncate = true;
       };
 
       systemd.tmpfiles.rules = [
diff --git a/nixos/modules/services/networking/powerdns.nix b/nixos/modules/services/networking/powerdns.nix
index 8cae61b835431..b035698456c05 100644
--- a/nixos/modules/services/networking/powerdns.nix
+++ b/nixos/modules/services/networking/powerdns.nix
@@ -24,14 +24,14 @@ in {
 
   config = mkIf cfg.enable {
 
-    systemd.packages = [ pkgs.powerdns ];
+    systemd.packages = [ pkgs.pdns ];
 
     systemd.services.pdns = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" "mysql.service" "postgresql.service" "openldap.service" ];
 
       serviceConfig = {
-        ExecStart = [ "" "${pkgs.powerdns}/bin/pdns_server --config-dir=${configDir} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no" ];
+        ExecStart = [ "" "${pkgs.pdns}/bin/pdns_server --config-dir=${configDir} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no" ];
       };
     };
 
diff --git a/nixos/modules/services/networking/syncplay.nix b/nixos/modules/services/networking/syncplay.nix
index b6faf2d3f7727..c17426ecced72 100644
--- a/nixos/modules/services/networking/syncplay.nix
+++ b/nixos/modules/services/networking/syncplay.nix
@@ -61,6 +61,15 @@ in
           Group to use when running Syncplay.
         '';
       };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Path to the file that contains the server password. If
+          <literal>null</literal>, the server doesn't require a password.
+        '';
+      };
     };
   };
 
@@ -71,10 +80,17 @@ in
       after       = [ "network-online.target" ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.syncplay}/bin/syncplay-server ${escapeShellArgs cmdArgs}";
         User = cfg.user;
         Group = cfg.group;
+        LoadCredential = lib.mkIf (cfg.passwordFile != null) "password:${cfg.passwordFile}";
       };
+
+      script = ''
+        ${lib.optionalString (cfg.passwordFile != null) ''
+          export SYNCPLAY_PASSWORD=$(cat "''${CREDENTIALS_DIRECTORY}/password")
+        ''}
+        exec ${pkgs.syncplay}/bin/syncplay-server ${escapeShellArgs cmdArgs}
+      '';
     };
   };
 }
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index ce295bd4ba3bb..5c89d58723760 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -571,8 +571,11 @@ in
     users.users.oauth2_proxy = {
       description = "OAuth2 Proxy";
       isSystemUser = true;
+      group = "oauth2_proxy";
     };
 
+    users.groups.oauth2_proxy = {};
+
     systemd.services.oauth2_proxy = {
       description = "OAuth2 Proxy";
       path = [ cfg.package ];
diff --git a/nixos/modules/services/security/sslmate-agent.nix b/nixos/modules/services/security/sslmate-agent.nix
new file mode 100644
index 0000000000000..c850eb22a0311
--- /dev/null
+++ b/nixos/modules/services/security/sslmate-agent.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sslmate-agent;
+
+in {
+  meta.maintainers = with maintainers; [ wolfangaukang ];
+
+  options = {
+    services.sslmate-agent = {
+      enable = mkEnableOption "sslmate-agent, a daemon for managing SSL/TLS certificates on a server";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ sslmate-agent ];
+
+    systemd = {
+      packages = [ pkgs.sslmate-agent ];
+      services.sslmate-agent = {
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ConfigurationDirectory = "sslmate-agent";
+          LogsDirectory = "sslmate-agent";
+          StateDirectory = "sslmate-agent";
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/video/unifi-video.nix b/nixos/modules/services/video/unifi-video.nix
index 43208a9fe4cfb..11d9fe305470c 100644
--- a/nixos/modules/services/video/unifi-video.nix
+++ b/nixos/modules/services/video/unifi-video.nix
@@ -16,7 +16,7 @@ let
     -pidfile ${cfg.pidFile} \
     -procname unifi-video \
     -Djava.security.egd=file:/dev/./urandom \
-    -Xmx${cfg.maximumJavaHeapSize}M \
+    -Xmx${toString cfg.maximumJavaHeapSize}M \
     -Xss512K \
     -XX:+UseG1GC \
     -XX:+UseStringDeduplication \
@@ -91,98 +91,102 @@ let
   stateDir = "/var/lib/unifi-video";
 
 in
-  {
-
-    options.services.unifi-video = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether or not to enable the unifi-video service.
-        '';
-      };
+{
 
-      jrePackage = mkOption {
-        type = types.package;
-        default = pkgs.jre8;
-        defaultText = literalExpression "pkgs.jre8";
-        description = ''
-          The JRE package to use. Check the release notes to ensure it is supported.
-        '';
-      };
+  options.services.unifi-video = {
 
-      unifiVideoPackage = mkOption {
-        type = types.package;
-        default = pkgs.unifi-video;
-        defaultText = literalExpression "pkgs.unifi-video";
-        description = ''
-          The unifi-video package to use.
-        '';
-      };
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether or not to enable the unifi-video service.
+      '';
+    };
 
-      mongodbPackage = mkOption {
-        type = types.package;
-        default = pkgs.mongodb-4_0;
-        defaultText = literalExpression "pkgs.mongodb";
-        description = ''
-          The mongodb package to use.
-        '';
-      };
+    jrePackage = mkOption {
+      type = types.package;
+      default = pkgs.jre8;
+      defaultText = literalExpression "pkgs.jre8";
+      description = ''
+        The JRE package to use. Check the release notes to ensure it is supported.
+      '';
+    };
 
-      logDir = mkOption {
-        type = types.str;
-        default = "${stateDir}/logs";
-        description = ''
-          Where to store the logs.
-        '';
-      };
+    unifiVideoPackage = mkOption {
+      type = types.package;
+      default = pkgs.unifi-video;
+      defaultText = literalExpression "pkgs.unifi-video";
+      description = ''
+        The unifi-video package to use.
+      '';
+    };
 
-      dataDir = mkOption {
-        type = types.str;
-        default = "${stateDir}/data";
-        description = ''
-          Where to store the database and other data.
-        '';
-      };
+    mongodbPackage = mkOption {
+      type = types.package;
+      default = pkgs.mongodb-4_0;
+      defaultText = literalExpression "pkgs.mongodb";
+      description = ''
+        The mongodb package to use.
+      '';
+    };
 
-      openPorts = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether or not to open the required ports on the firewall.
-        '';
-      };
+    logDir = mkOption {
+      type = types.str;
+      default = "${stateDir}/logs";
+      description = ''
+        Where to store the logs.
+      '';
+    };
 
-      maximumJavaHeapSize = mkOption {
-        type = types.nullOr types.int;
-        default = 1024;
-        example = 4096;
-        description = ''
-          Set the maximimum heap size for the JVM in MB.
-        '';
-      };
+    dataDir = mkOption {
+      type = types.str;
+      default = "${stateDir}/data";
+      description = ''
+        Where to store the database and other data.
+      '';
+    };
 
-      pidFile = mkOption {
-        type = types.path;
-        default = "${cfg.dataDir}/unifi-video.pid";
-        defaultText = literalExpression ''"''${config.${opt.dataDir}}/unifi-video.pid"'';
-        description = "Location of unifi-video pid file.";
-      };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether or not to open the required ports on the firewall.
+      '';
+    };
+
+    maximumJavaHeapSize = mkOption {
+      type = types.nullOr types.int;
+      default = 1024;
+      example = 4096;
+      description = ''
+        Set the maximimum heap size for the JVM in MB.
+      '';
+    };
+
+    pidFile = mkOption {
+      type = types.path;
+      default = "${cfg.dataDir}/unifi-video.pid";
+      defaultText = literalExpression ''"''${config.${opt.dataDir}}/unifi-video.pid"'';
+      description = "Location of unifi-video pid file.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
 
-};
+    warnings = optional
+      (options.services.unifi-video.openFirewall.highestPrio >= (mkOptionDefault null).priority)
+      "The current services.unifi-video.openFirewall = true default is deprecated and will change to false in 22.11. Set it explicitly to silence this warning.";
 
-config = mkIf cfg.enable {
-  users = {
-    users.unifi-video = {
+    users.users.unifi-video = {
       description = "UniFi Video controller daemon user";
       home = stateDir;
       group = "unifi-video";
       isSystemUser = true;
     };
-    groups.unifi-video = {};
-  };
+    users.groups.unifi-video = {};
 
-  networking.firewall = mkIf cfg.openPorts {
+    networking.firewall = mkIf cfg.openFirewall {
       # https://help.ui.com/hc/en-us/articles/217875218-UniFi-Video-Ports-Used
       allowedTCPPorts = [
         7080 # HTTP portal
@@ -237,7 +241,6 @@ config = mkIf cfg.enable {
       "L+ '${stateDir}/conf/server.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/server.xml"
       "L+ '${stateDir}/conf/tomcat-users.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/tomcat-users.xml"
       "L+ '${stateDir}/conf/web.xml' 0700 unifi-video unifi-video - ${pkgs.unifi-video}/lib/unifi-video/conf/web.xml"
-
     ];
 
     systemd.services.unifi-video = {
@@ -258,10 +261,11 @@ config = mkIf cfg.enable {
         WorkingDirectory = "${stateDir}";
       };
     };
-
   };
 
-  meta = {
-    maintainers = with lib.maintainers; [ rsynnest ];
-  };
+  imports = [
+    (mkRenamedOptionModule [ "services" "unifi-video" "openPorts" ] [ "services" "unifi-video" "openFirewall" ])
+  ];
+
+  meta.maintainers = with lib.maintainers; [ rsynnest ];
 }
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 2f1c4acec1e8b..be0b5b94fb26c 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -159,7 +159,7 @@ in
       '';
     };
 
-    caddy.enable = mkEnableOption "Whether to enablle caddy reverse proxy to expose jitsi-meet";
+    caddy.enable = mkEnableOption "Whether to enable caddy reverse proxy to expose jitsi-meet";
 
     prosody.enable = mkOption {
       type = bool;
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index 8208c85bfd708..fbfcc33b2dce0 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -9,6 +9,8 @@ let
     RAILS_ENV = "production";
     NODE_ENV = "production";
 
+    LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
+
     # mastodon-web concurrency.
     WEB_CONCURRENCY = toString cfg.webProcesses;
     MAX_THREADS = toString cfg.webThreads;
@@ -121,7 +123,7 @@ in {
 
           Make sure that websockets are forwarded properly. You might want to set up caching
           of some requests. Take a look at mastodon's provided nginx configuration at
-          <code>https://github.com/tootsuite/mastodon/blob/master/dist/nginx.conf</code>.
+          <code>https://github.com/mastodon/mastodon/blob/master/dist/nginx.conf</code>.
         '';
         type = lib.types.bool;
         default = false;
diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
new file mode 100644
index 0000000000000..a7d8bede74b4b
--- /dev/null
+++ b/nixos/modules/services/web-apps/netbox.nix
@@ -0,0 +1,265 @@
+{ config, lib, pkgs, buildEnv, ... }:
+
+with lib;
+
+let
+  cfg = config.services.netbox;
+  staticDir = cfg.dataDir + "/static";
+  configFile = pkgs.writeTextFile {
+    name = "configuration.py";
+    text = ''
+      STATIC_ROOT = '${staticDir}'
+      ALLOWED_HOSTS = ['*']
+      DATABASE = {
+        'NAME': 'netbox',
+        'USER': 'netbox',
+        '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': {
+              'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=0',
+              'SSL': False,
+          },
+          'caching': {
+              'URL': 'unix://${config.services.redis.servers.netbox.unixSocket}?db=1',
+              'SSL': False,
+          }
+      }
+
+      with open("${cfg.secretKeyFile}", "r") as file:
+          SECRET_KEY = file.readline()
+
+      ${optionalString cfg.enableLdap "REMOTE_AUTH_BACKEND = 'netbox.authentication.LDAPBackend'"}
+
+      ${cfg.extraConfig}
+    '';
+  };
+  pkg = (pkgs.netbox.overrideAttrs (old: {
+    installPhase = old.installPhase + ''
+      ln -s ${configFile} $out/opt/netbox/netbox/netbox/configuration.py
+    '' + optionalString cfg.enableLdap ''
+      ln -s ${ldapConfigPath} $out/opt/netbox/netbox/netbox/ldap_config.py
+    '';
+  })).override {
+    plugins = ps: ((cfg.plugins ps)
+      ++ optional cfg.enableLdap [ ps.django-auth-ldap ]);
+  };
+  netboxManageScript = with pkgs; (writeScriptBin "netbox-manage" ''
+    #!${stdenv.shell}
+    export PYTHONPATH=${pkg.pythonPath}
+    sudo -u netbox ${pkg}/bin/netbox "$@"
+  '');
+
+in {
+  options.services.netbox = {
+    enable = mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Enable Netbox.
+
+        This module requires a reverse proxy that serves <literal>/static</literal> separately.
+        See this <link xlink:href="https://github.com/netbox-community/netbox/blob/develop/contrib/nginx.conf/">example</link> on how to configure this.
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "[::1]";
+      description = ''
+        Address the server will listen on.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8001;
+      description = ''
+        Port the server will listen on.
+      '';
+    };
+
+    plugins = mkOption {
+      type = types.functionTo (types.listOf types.package);
+      default = _: [];
+      defaultText = literalExpression ''
+        python3Packages: with python3Packages; [];
+      '';
+      description = ''
+        List of plugin packages to install.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/netbox";
+      description = ''
+        Storage path of netbox.
+      '';
+    };
+
+    secretKeyFile = mkOption {
+      type = types.path;
+      description = ''
+        Path to a file containing the secret key.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Additional lines of configuration appended to the <literal>configuration.py</literal>.
+        See the <link xlink:href="https://netbox.readthedocs.io/en/stable/configuration/optional-settings/">documentation</link> for more possible options.
+      '';
+    };
+
+    enableLdap = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable LDAP-Authentication for Netbox.
+
+        This requires a configuration file being pass through <literal>ldapConfigPath</literal>.
+      '';
+    };
+
+    ldapConfigPath = mkOption {
+      type = types.path;
+      default = "";
+      description = ''
+        Path to the Configuration-File for LDAP-Authentification, will be loaded as <literal>ldap_config.py</literal>.
+        See the <link xlink:href="https://netbox.readthedocs.io/en/stable/installation/6-ldap/#configuration">documentation</link> for possible options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.redis.servers.netbox.enable = true;
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "netbox" ];
+      ensureUsers = [
+        {
+          name = "netbox";
+          ensurePermissions = {
+            "DATABASE netbox" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+    };
+
+    environment.systemPackages = [ netboxManageScript ];
+
+    systemd.targets.netbox = {
+      description = "Target for all NetBox services";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" "redis-netbox.service" ];
+    };
+
+    systemd.services = let
+      defaultServiceConfig = {
+        WorkingDirectory = "${cfg.dataDir}";
+        User = "netbox";
+        Group = "netbox";
+        StateDirectory = "netbox";
+        StateDirectoryMode = "0750";
+        Restart = "on-failure";
+      };
+    in {
+      netbox-migration = {
+        description = "NetBox migrations";
+        wantedBy = [ "netbox.target" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          Type = "oneshot";
+          ExecStart = ''
+            ${pkg}/bin/netbox migrate
+          '';
+        };
+      };
+
+      netbox = {
+        description = "NetBox WSGI Service";
+        wantedBy = [ "netbox.target" ];
+        after = [ "netbox-migration.service" ];
+
+        preStart = ''
+          ${pkg}/bin/netbox trace_paths --no-input
+          ${pkg}/bin/netbox collectstatic --no-input
+          ${pkg}/bin/netbox remove_stale_contenttypes --no-input
+        '';
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          ExecStart = ''
+            ${pkgs.python3Packages.gunicorn}/bin/gunicorn netbox.wsgi \
+              --bind ${cfg.listenAddress}:${toString cfg.port} \
+              --pythonpath ${pkg}/opt/netbox/netbox
+          '';
+        };
+      };
+
+      netbox-rq = {
+        description = "NetBox Request Queue Worker";
+        wantedBy = [ "netbox.target" ];
+        after = [ "netbox.service" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          ExecStart = ''
+            ${pkg}/bin/netbox rqworker high default low
+          '';
+        };
+      };
+
+      netbox-housekeeping = {
+        description = "NetBox housekeeping job";
+        after = [ "netbox.service" ];
+
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+
+        serviceConfig = defaultServiceConfig // {
+          Type = "oneshot";
+          ExecStart = ''
+            ${pkg}/bin/netbox housekeeping
+          '';
+        };
+      };
+    };
+
+    systemd.timers.netbox-housekeeping = {
+      description = "Run NetBox housekeeping job";
+      wantedBy = [ "timers.target" ];
+
+      timerConfig = {
+        OnCalendar = "daily";
+      };
+    };
+
+    users.users.netbox = {
+      home = "${cfg.dataDir}";
+      isSystemUser = true;
+      group = "netbox";
+    };
+    users.groups.netbox = {};
+    users.groups."${config.services.redis.servers.netbox.user}".members = [ "netbox" ];
+  };
+}
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index d817ff6019a3b..3099705acbe2e 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -710,20 +710,15 @@ in
 
     services.logrotate = optionalAttrs (cfg.logFormat != "none") {
       enable = mkDefault true;
-      paths.httpd = {
-        path = "${cfg.logDir}/*.log";
-        user = cfg.user;
-        group = cfg.group;
+      settings.httpd = {
+        files = "${cfg.logDir}/*.log";
+        su = "${cfg.user} ${cfg.group}";
         frequency = "daily";
-        keep = 28;
-        extraConfig = ''
-          sharedscripts
-          compress
-          delaycompress
-          postrotate
-            systemctl reload httpd.service > /dev/null 2>/dev/null || true
-          endscript
-        '';
+        rotate = 28;
+        sharedscripts = true;
+        compress = true;
+        delaycompress = true;
+        postrotate = "systemctl reload httpd.service > /dev/null 2>/dev/null || true";
       };
     };
 
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index e046c28dd6bbe..7caaf5611cc0e 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -989,17 +989,14 @@ in
       nginx.gid = config.ids.gids.nginx;
     };
 
-    services.logrotate.paths.nginx = mapAttrs (_: mkDefault) {
-      path = "/var/log/nginx/*.log";
+    services.logrotate.settings.nginx = mapAttrs (_: mkDefault) {
+      files = "/var/log/nginx/*.log";
       frequency = "weekly";
-      keep = 26;
-      extraConfig = ''
-        compress
-        delaycompress
-        postrotate
-          [ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`
-        endscript
-      '';
+      su = "${cfg.user} ${cfg.group}";
+      rotate = 26;
+      compress = true;
+      delaycompress = true;
+      postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
     };
   };
 }
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index b308ed736ff84..f87258ac8dc56 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -498,6 +498,10 @@ in
       programs.seahorse.enable = notExcluded pkgs.gnome.seahorse;
       services.gnome.sushi.enable = notExcluded pkgs.gnome.sushi;
 
+      # VTE shell integration for gnome-console
+      programs.bash.vteIntegration = mkDefault true;
+      programs.zsh.vteIntegration = mkDefault true;
+
       # Let nautilus find extensions
       # TODO: Create nautilus-with-extensions package
       environment.sessionVariables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0";
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 48e119a861875..3528b0f40e7b7 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -302,6 +302,7 @@ in
       environment.systemPackages = with pkgs.pantheon; [
         contractor
         file-roller-contract
+        gnome-bluetooth-contract
       ];
 
       environment.pathsToLink = [
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 3cf92f98c56fd..0bb5c1268cac2 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -99,6 +99,7 @@ in
       ristretto
       xfce4-appfinder
       xfce4-notifyd
+      xfce4-screensaver
       xfce4-screenshooter
       xfce4-session
       xfce4-settings
@@ -168,5 +169,6 @@ in
       xfce4-notifyd
     ];
 
+    security.pam.services.xfce4-screensaver.unixAuth = true;
   };
 }
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index db00244ca0afa..fad00e39497d9 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -241,7 +241,7 @@ in
             "xhci_pci"
             "usbhid"
             "hid_generic" "hid_lenovo" "hid_apple" "hid_roccat"
-            "hid_logitech_hidpp" "hid_logitech_dj" "hid_microsoft"
+            "hid_logitech_hidpp" "hid_logitech_dj" "hid_microsoft" "hid_cherry"
 
           ] ++ optionals pkgs.stdenv.hostPlatform.isx86 [
             # Misc. x86 keyboard stuff.
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index f0d3170dc5ac2..dde07571b3e7d 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -877,7 +877,7 @@ in
         copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
         copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
 
-        cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
+        cc -O3 -I${pkgs.openssl.dev}/include -L${lib.getLib pkgs.openssl}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
         strip -s pbkdf2-sha512
         copy_bin_and_libs pbkdf2-sha512
 
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index ac1e4ef34b46f..092b7b8863aed 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -1741,6 +1741,48 @@ in
         }));
     };
 
+    systemd.network.wait-online = {
+      anyInterface = mkOption {
+        description = ''
+          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.
+        '';
+        type = types.bool;
+        default = false;
+      };
+
+      ignoredInterfaces = mkOption {
+        description = ''
+          Network interfaces to be ignored when deciding if the system is online.
+        '';
+        type = with types; listOf str;
+        default = [];
+        example = [ "wg0" ];
+      };
+
+      timeout = mkOption {
+        description = ''
+          Time to wait for the network to come online, in seconds. Set to 0 to disable.
+        '';
+        type = types.ints.unsigned;
+        default = 120;
+        example = 0;
+      };
+
+      extraArgs = mkOption {
+        description = ''
+          Extra command-line arguments to pass to systemd-networkd-wait-online.
+          These also affect per-interface <literal>systemd-network-wait-online@</literal> services.
+
+          See <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd-networkd-wait-online.service.html">
+          <citerefentry><refentrytitle>systemd-networkd-wait-online.service</refentrytitle><manvolnum>8</manvolnum>
+          </citerefentry></link> for all available options.
+        '';
+        type = with types; listOf str;
+        default = [];
+      };
+    };
+
   };
 
   config = mkMerge [
@@ -1749,6 +1791,11 @@ in
     {
       systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links;
       environment.etc = unitFiles;
+
+      systemd.network.wait-online.extraArgs =
+        [ "--timeout=${toString cfg.wait-online.timeout}" ]
+        ++ optional cfg.wait-online.anyInterface "--any"
+        ++ map (i: "--ignore=${i}") cfg.wait-online.ignoredInterfaces;
     }
 
     (mkIf config.systemd.network.enable {
@@ -1777,6 +1824,10 @@ in
 
       systemd.services.systemd-networkd-wait-online = {
         wantedBy = [ "network-online.target" ];
+        serviceConfig.ExecStart = [
+          ""
+          "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online ${utils.escapeSystemdExecArgs cfg.wait-online.extraArgs}"
+        ];
       };
 
       systemd.services."systemd-network-wait-online@" = {
@@ -1787,7 +1838,7 @@ in
         serviceConfig = {
           Type = "oneshot";
           RemainAfterExit = true;
-          ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I";
+          ExecStart = "${config.systemd.package}/lib/systemd/systemd-networkd-wait-online -i %I ${utils.escapeSystemdExecArgs cfg.wait-online.extraArgs}";
         };
       };
 
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 8b011d91563f0..1bafec30b53d4 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -703,8 +703,12 @@ in
       }
     ];
 
-    system.build =
-      { inherit bootStage1 initialRamdisk initialRamdiskSecretAppender extraUtils; };
+    system.build = mkMerge [
+      { inherit bootStage1 initialRamdiskSecretAppender extraUtils; }
+
+      # generated in nixos/modules/system/boot/systemd/initrd.nix
+      (mkIf (!config.boot.initrd.systemd.enable) { inherit initialRamdisk; })
+    ];
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isYes "TMPFS")
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index a90f58042d2d6..9f8d86da6b003 100755
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -12,10 +12,6 @@ for o in $(</proc/cmdline); do
             # Show each command.
             set -x
             ;;
-        resume=*)
-            set -- $(IFS==; echo $o)
-            resumeDevice=$2
-            ;;
     esac
 done
 
@@ -72,46 +68,12 @@ if [ -n "@readOnlyStore@" ]; then
 fi
 
 
-# Provide a /etc/mtab.
-install -m 0755 -d /etc
-test -e /etc/fstab || touch /etc/fstab # to shut up mount
-rm -f /etc/mtab* # not that we care about stale locks
-ln -s /proc/mounts /etc/mtab
-
-
-# More special file systems, initialise required directories.
-[ -e /proc/bus/usb ] && mount -t usbfs usbfs /proc/bus/usb # UML doesn't have USB by default
-install -m 01777 -d /tmp
-install -m 0755 -d /var/{log,lib,db} /nix/var /etc/nixos/ \
-    /run/lock /home /bin # for the /bin/sh symlink
-
-
-# Miscellaneous boot time cleanup.
-rm -rf /var/run /var/lock
-rm -f /etc/{group,passwd,shadow}.lock
-
-
-# Also get rid of temporary GC roots.
-rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots
-
-
-# For backwards compatibility, symlink /var/run to /run, and /var/lock
-# to /run/lock.
-ln -s /run /var/run
-ln -s /run/lock /var/lock
-
-
-# Clear the resume device.
-if test -n "$resumeDevice"; then
-    mkswap "$resumeDevice" || echo 'Failed to clear saved image.'
-fi
-
-
 # Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
 if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then
     resolvconf -m 1000 -a host </etc/resolv.conf
 fi
 
+
 # Log the script output to /dev/kmsg or /run/log/stage-2-init.log.
 # Only at this point are all the necessary prerequisites ready for these commands.
 exec {logOutFd}>&1 {logErrFd}>&2
@@ -133,22 +95,9 @@ echo "running activation script..."
 $systemConfig/activate
 
 
-# Restore the system time from the hardware clock.  We do this after
-# running the activation script to be sure that /etc/localtime points
-# at the current time zone.
-if [ -e /dev/rtc ]; then
-    hwclock --hctosys
-fi
-
-
 # Record the boot configuration.
 ln -sfn "$systemConfig" /run/booted-system
 
-# Prevent the booted system from being garbage-collected. If it weren't
-# a gcroot, if we were running a different kernel, switched system,
-# and garbage collected all, we could not load kernel modules anymore.
-ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
-
 
 # Run any user-specified commands.
 @shell@ @postBootCommands@
@@ -167,10 +116,6 @@ exec 1>&$logOutFd 2>&$logErrFd
 exec {logOutFd}>&- {logErrFd}>&-
 
 
-# Start systemd.
+# Start systemd in a clean environment.
 echo "starting systemd..."
-
-PATH=/run/current-system/systemd/lib/systemd:@fsPackagesPath@ \
-    LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive @systemdUnitPathEnvVar@ \
-    TZDIR=/etc/zoneinfo \
-    exec @systemdExecutable@
+exec @systemdExecutable@ "$@"
diff --git a/nixos/modules/system/boot/stage-2.nix b/nixos/modules/system/boot/stage-2.nix
index f6b6a8e4b0b44..f6461daf31163 100644
--- a/nixos/modules/system/boot/stage-2.nix
+++ b/nixos/modules/system/boot/stage-2.nix
@@ -19,11 +19,6 @@ let
       pkgs.coreutils
       pkgs.util-linux
     ] ++ lib.optional useHostResolvConf pkgs.openresolv);
-    fsPackagesPath = lib.makeBinPath config.system.fsPackages;
-    systemdUnitPathEnvVar = lib.optionalString (config.boot.extraSystemdUnitPaths != [])
-      ("SYSTEMD_UNIT_PATH="
-      + builtins.concatStringsSep ":" config.boot.extraSystemdUnitPaths
-      + ":"); # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable
     postBootCommands = pkgs.writeText "local-cmds"
       ''
         ${config.boot.postBootCommands}
@@ -47,43 +42,11 @@ in
         '';
       };
 
-      devSize = mkOption {
-        default = "5%";
-        example = "32m";
-        type = types.str;
-        description = ''
-          Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
-          for the accepted syntax.
-        '';
-      };
-
-      devShmSize = mkOption {
-        default = "50%";
-        example = "256m";
-        type = types.str;
-        description = ''
-          Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
-          for the accepted syntax.
-        '';
-      };
-
-      runSize = mkOption {
-        default = "25%";
-        example = "256m";
-        type = types.str;
-        description = ''
-          Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
-          for the accepted syntax.
-        '';
-      };
-
       systemdExecutable = mkOption {
-        default = "systemd";
+        default = "/run/current-system/systemd/lib/systemd/systemd";
         type = types.str;
         description = ''
-          The program to execute to start systemd. Typically
-          <literal>systemd</literal>, which will find systemd in the
-          PATH.
+          The program to execute to start systemd.
         '';
       };
 
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 057474c607ac8..844a6793c1540 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -11,14 +11,7 @@ let
   systemd = cfg.package;
 
   inherit (systemdUtils.lib)
-    makeUnit
     generateUnits
-    makeJobScript
-    unitConfig
-    serviceConfig
-    mountConfig
-    automountConfig
-    commonUnitText
     targetToUnit
     serviceToUnit
     socketToUnit
@@ -185,13 +178,7 @@ in
     systemd.units = mkOption {
       description = "Definition of systemd units.";
       default = {};
-      type = with types; attrsOf (submodule (
-        { name, config, ... }:
-        { options = concreteUnitOptions;
-          config = {
-            unit = mkDefault (makeUnit name config);
-          };
-        }));
+      type = systemdUtils.types.units;
     };
 
     systemd.packages = mkOption {
@@ -203,37 +190,37 @@ in
 
     systemd.targets = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+      type = systemdUtils.types.targets;
       description = "Definition of systemd target units.";
     };
 
     systemd.services = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ]);
+      type = systemdUtils.types.services;
       description = "Definition of systemd service units.";
     };
 
     systemd.sockets = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ]);
+      type = systemdUtils.types.sockets;
       description = "Definition of systemd socket units.";
     };
 
     systemd.timers = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ]);
+      type = systemdUtils.types.timers;
       description = "Definition of systemd timer units.";
     };
 
     systemd.paths = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+      type = systemdUtils.types.paths;
       description = "Definition of systemd path units.";
     };
 
     systemd.mounts = mkOption {
       default = [];
-      type = with types; listOf (submodule [ { options = mountOptions; } unitConfig mountConfig ]);
+      type = systemdUtils.types.mounts;
       description = ''
         Definition of systemd mount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -243,7 +230,7 @@ in
 
     systemd.automounts = mkOption {
       default = [];
-      type = with types; listOf (submodule [ { options = automountOptions; } unitConfig automountConfig ]);
+      type = systemdUtils.types.automounts;
       description = ''
         Definition of systemd automount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -253,7 +240,7 @@ in
 
     systemd.slices = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig] );
+      type = systemdUtils.types.slices;
       description = "Definition of slice configurations.";
     };
 
@@ -302,6 +289,16 @@ in
       '';
     };
 
+    systemd.managerEnvironment = mkOption {
+      type = with types; attrsOf (nullOr (oneOf [ str path package ]));
+      default = {};
+      example = { SYSTEMD_LOG_LEVEL = "debug"; };
+      description = ''
+        Environment variables of PID 1. These variables are
+        <emphasis>not</emphasis> passed to started units.
+      '';
+    };
+
     systemd.enableCgroupAccounting = mkOption {
       default = true;
       type = types.bool;
@@ -352,10 +349,11 @@ in
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
       description = ''
-        A list of units to suppress when generating system systemd configuration directory. This has
+        A list of units to skip when generating system systemd configuration directory. This has
         priority over upstream units, <option>systemd.units</option>, and
         <option>systemd.additionalUpstreamSystemUnits</option>. The main purpose of this is to
-        suppress a upstream systemd unit with any modifications made to it by other NixOS modules.
+        prevent a upstream systemd unit from being added to the initrd with any modifications made to it
+        by other NixOS modules.
       '';
     };
 
@@ -470,11 +468,18 @@ in
 
       enabledUpstreamSystemUnits = filter (n: ! elem n cfg.suppressedSystemUnits) upstreamSystemUnits;
       enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units;
+
     in ({
-      "systemd/system".source = generateUnits "system" enabledUnits enabledUpstreamSystemUnits upstreamSystemWants;
+      "systemd/system".source = generateUnits {
+        type = "system";
+        units = enabledUnits;
+        upstreamUnits = enabledUpstreamSystemUnits;
+        upstreamWants = upstreamSystemWants;
+      };
 
       "systemd/system.conf".text = ''
         [Manager]
+        ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
         ${optionalString config.systemd.enableCgroupAccounting ''
           DefaultCPUAccounting=yes
           DefaultIOAccounting=yes
@@ -542,6 +547,17 @@ in
                    (v: let n = escapeSystemdPath v.where;
                        in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
 
+      # Environment of PID 1
+      systemd.managerEnvironment = {
+        # Doesn't contain systemd itself - everything works so it seems to use the compiled-in value for its tools
+        PATH = lib.makeBinPath config.system.fsPackages;
+        LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
+        TZDIR = "/etc/zoneinfo";
+        # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable
+        SYSTEMD_UNIT_PATH = lib.mkIf (config.boot.extraSystemdUnitPaths != []) "${builtins.concatStringsSep ":" config.boot.extraSystemdUnitPaths}:";
+      };
+
+
     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"
@@ -589,22 +605,18 @@ in
 
     boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
 
-    services.logrotate.paths = {
+    services.logrotate.settings = {
       "/var/log/btmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
-        keep = 1;
-        extraConfig = ''
-          create 0660 root ${config.users.groups.utmp.name}
-          minsize 1M
-        '';
+        rotate = 1;
+        create = "0660 root ${config.users.groups.utmp.name}";
+        minsize = "1M";
       };
       "/var/log/wtmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
-        keep = 1;
-        extraConfig = ''
-          create 0664 root ${config.users.groups.utmp.name}
-          minsize 1M
-        '';
+        rotate = 1;
+        create = "0664 root ${config.users.groups.utmp.name}";
+        minsize = "1M";
       };
     };
   };
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
new file mode 100644
index 0000000000000..30bdc9a3422c7
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -0,0 +1,417 @@
+{ lib, config, utils, pkgs, ... }:
+
+with lib;
+
+let
+  inherit (utils) systemdUtils escapeSystemdPath;
+  inherit (systemdUtils.lib)
+    generateUnits
+    pathToUnit
+    serviceToUnit
+    sliceToUnit
+    socketToUnit
+    targetToUnit
+    timerToUnit
+    mountToUnit
+    automountToUnit;
+
+
+  cfg = config.boot.initrd.systemd;
+
+  # Copied from fedora
+  upstreamUnits = [
+    "basic.target"
+    "ctrl-alt-del.target"
+    "emergency.service"
+    "emergency.target"
+    "final.target"
+    "halt.target"
+    "initrd-cleanup.service"
+    "initrd-fs.target"
+    "initrd-parse-etc.service"
+    "initrd-root-device.target"
+    "initrd-root-fs.target"
+    "initrd-switch-root.service"
+    "initrd-switch-root.target"
+    "initrd.target"
+    "initrd-udevadm-cleanup-db.service"
+    "kexec.target"
+    "kmod-static-nodes.service"
+    "local-fs-pre.target"
+    "local-fs.target"
+    "multi-user.target"
+    "paths.target"
+    "poweroff.target"
+    "reboot.target"
+    "rescue.service"
+    "rescue.target"
+    "rpcbind.target"
+    "shutdown.target"
+    "sigpwr.target"
+    "slices.target"
+    "sockets.target"
+    "swap.target"
+    "sysinit.target"
+    "sys-kernel-config.mount"
+    "syslog.socket"
+    "systemd-ask-password-console.path"
+    "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"
+    "systemd-journald.socket"
+    "systemd-kexec.service"
+    "systemd-modules-load.service"
+    "systemd-poweroff.service"
+    "systemd-random-seed.service"
+    "systemd-reboot.service"
+    "systemd-sysctl.service"
+    "systemd-tmpfiles-setup-dev.service"
+    "systemd-tmpfiles-setup.service"
+    "systemd-udevd-control.socket"
+    "systemd-udevd-kernel.socket"
+    "systemd-udevd.service"
+    "systemd-udev-settle.service"
+    "systemd-udev-trigger.service"
+    "systemd-vconsole-setup.service"
+    "timers.target"
+    "umount.target"
+
+    # TODO: Networking
+    # "network-online.target"
+    # "network-pre.target"
+    # "network.target"
+    # "nss-lookup.target"
+    # "nss-user-lookup.target"
+    # "remote-fs-pre.target"
+    # "remote-fs.target"
+  ] ++ cfg.additionalUpstreamUnits;
+
+  upstreamWants = [
+    "sysinit.target.wants"
+  ];
+
+  enabledUpstreamUnits = filter (n: ! elem n cfg.suppressedUnits) upstreamUnits;
+  enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedUnits) cfg.units;
+
+  stage1Units = generateUnits {
+    type = "initrd";
+    units = enabledUnits;
+    upstreamUnits = enabledUpstreamUnits;
+    inherit upstreamWants;
+    inherit (cfg) packages package;
+  };
+
+  fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
+
+  fstab = pkgs.writeText "fstab" (lib.concatMapStringsSep "\n"
+    ({ fsType, mountPoint, device, options, autoFormat, autoResize, ... }@fs: let
+        opts = options ++ optional autoFormat "x-systemd.makefs" ++ optional autoResize "x-systemd.growfs";
+      in "${device} /sysroot${mountPoint} ${fsType} ${lib.concatStringsSep "," opts}") fileSystems);
+
+  kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
+  modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
+  firmware = config.hardware.firmware;
+  # Determine the set of modules that we need to mount the root FS.
+  modulesClosure = pkgs.makeModulesClosure {
+    rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules;
+    kernel = modulesTree;
+    firmware = firmware;
+    allowMissing = false;
+  };
+
+  initrdBinEnv = pkgs.buildEnv {
+    name = "initrd-emergency-env";
+    paths = map getBin cfg.initrdBin;
+    pathsToLink = ["/bin" "/sbin"];
+    # Make recovery easier
+    postBuild = ''
+      ln -s ${cfg.package.util-linux}/bin/mount $out/bin/
+      ln -s ${cfg.package.util-linux}/bin/umount $out/bin/
+    '';
+  };
+
+  initialRamdisk = pkgs.makeInitrdNG {
+    contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths)
+      ++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents);
+  };
+
+in {
+  options.boot.initrd.systemd = {
+    enable = mkEnableOption ''systemd in initrd.
+
+      Note: This is in very early development and is highly
+      experimental. Most of the features NixOS supports in initrd are
+      not yet supported by the intrd generated with this option.
+    '';
+
+    package = (mkPackageOption pkgs "systemd" {
+      default = "systemdMinimal";
+    }) // {
+      visible = false;
+    };
+
+    contents = mkOption {
+      description = "Set of files that have to be linked into the initrd";
+      example = literalExpression ''
+        {
+          "/etc/hostname".text = "mymachine";
+        }
+      '';
+      visible = false;
+      default = {};
+      type = types.attrsOf (types.submodule ({ config, options, name, ... }: {
+        options = {
+          enable = mkEnableOption "copying of this file to initrd and symlinking it" // { default = true; };
+
+          target = mkOption {
+            type = types.path;
+            description = ''
+              Path of the symlink.
+            '';
+            default = name;
+          };
+
+          text = mkOption {
+            default = null;
+            type = types.nullOr types.lines;
+            description = "Text of the file.";
+          };
+
+          source = mkOption {
+            type = types.path;
+            description = "Path of the source file.";
+          };
+        };
+
+        config = {
+          source = mkIf (config.text != null) (
+            let name' = "initrd-" + baseNameOf name;
+            in mkDerivedConfig options.text (pkgs.writeText name')
+          );
+        };
+      }));
+    };
+
+    storePaths = mkOption {
+      description = ''
+        Store paths to copy into the initrd as well.
+      '';
+      type = types.listOf types.singleLineStr;
+      default = [];
+    };
+
+    suppressedStorePaths = mkOption {
+      description = ''
+        Store paths specified in the storePaths option that
+        should not be copied.
+      '';
+      type = types.listOf types.singleLineStr;
+      default = [];
+    };
+
+    emergencyAccess = mkOption {
+      type = with types; oneOf [ bool singleLineStr ];
+      visible = false;
+      description = ''
+        Set to true for unauthenticated emergency access, and false for
+        no emergency access.
+
+        Can also be set to a hashed super user password to allow
+        authenticated access to the emergency mode.
+      '';
+      default = false;
+    };
+
+    initrdBin = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      visible = false;
+      description = ''
+        Packages to include in /bin for the stage 1 emergency shell.
+      '';
+    };
+
+    additionalUpstreamUnits = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      visible = false;
+      example = [ "debug-shell.service" "systemd-quotacheck.service" ];
+      description = ''
+        Additional units shipped with systemd that shall be enabled.
+      '';
+    };
+
+    suppressedUnits = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      example = [ "systemd-backlight@.service" ];
+      visible = false;
+      description = ''
+        A list of units to skip when generating system systemd configuration directory. This has
+        priority over upstream units, <option>boot.initrd.systemd.units</option>, and
+        <option>boot.initrd.systemd.additionalUpstreamUnits</option>. The main purpose of this is to
+        prevent a upstream systemd unit from being added to the initrd with any modifications made to it
+        by other NixOS modules.
+      '';
+    };
+
+    units = mkOption {
+      description = "Definition of systemd units.";
+      default = {};
+      visible = false;
+      type = systemdUtils.types.units;
+    };
+
+    packages = mkOption {
+      default = [];
+      visible = false;
+      type = types.listOf types.package;
+      example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
+      description = "Packages providing systemd units and hooks.";
+    };
+
+    targets = mkOption {
+      default = {};
+      visible = false;
+      type = systemdUtils.types.initrdTargets;
+      description = "Definition of systemd target units.";
+    };
+
+    services = mkOption {
+      default = {};
+      type = systemdUtils.types.initrdServices;
+      visible = false;
+      description = "Definition of systemd service units.";
+    };
+
+    sockets = mkOption {
+      default = {};
+      type = systemdUtils.types.initrdSockets;
+      visible = false;
+      description = "Definition of systemd socket units.";
+    };
+
+    timers = mkOption {
+      default = {};
+      type = systemdUtils.types.initrdTimers;
+      visible = false;
+      description = "Definition of systemd timer units.";
+    };
+
+    paths = mkOption {
+      default = {};
+      type = systemdUtils.types.initrdPaths;
+      visible = false;
+      description = "Definition of systemd path units.";
+    };
+
+    mounts = mkOption {
+      default = [];
+      type = systemdUtils.types.initrdMounts;
+      visible = false;
+      description = ''
+        Definition of systemd mount units.
+        This is a list instead of an attrSet, because systemd mandates the names to be derived from
+        the 'where' attribute.
+      '';
+    };
+
+    automounts = mkOption {
+      default = [];
+      type = systemdUtils.types.automounts;
+      visible = false;
+      description = ''
+        Definition of systemd automount units.
+        This is a list instead of an attrSet, because systemd mandates the names to be derived from
+        the 'where' attribute.
+      '';
+    };
+
+    slices = mkOption {
+      default = {};
+      type = systemdUtils.types.slices;
+      visible = false;
+      description = "Definition of slice configurations.";
+    };
+  };
+
+  config = mkIf (config.boot.initrd.enable && cfg.enable) {
+    system.build = { inherit initialRamdisk; };
+    boot.initrd.systemd = {
+      initrdBin = [pkgs.bash pkgs.coreutils pkgs.kmod cfg.package] ++ config.system.fsPackages;
+
+      contents = {
+        "/init".source = "${cfg.package}/lib/systemd/systemd";
+        "/etc/systemd/system".source = stage1Units;
+
+        "/etc/systemd/system.conf".text = ''
+          [Manager]
+          DefaultEnvironment=PATH=/bin:/sbin
+        '';
+
+        "/etc/fstab".source = fstab;
+
+        "/lib/modules".source = "${modulesClosure}/lib/modules";
+
+        "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules;
+
+        "/etc/passwd".source = "${pkgs.fakeNss}/etc/passwd";
+        "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then "!" else cfg.emergencyAccess}:::::::";
+
+        "/bin".source = "${initrdBinEnv}/bin";
+        "/sbin".source = "${initrdBinEnv}/sbin";
+
+        "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe";
+      };
+
+      storePaths = [
+        # TODO: Limit this to the bare necessities
+        "${cfg.package}/lib"
+
+        "${cfg.package.util-linux}/bin/mount"
+        "${cfg.package.util-linux}/bin/umount"
+        "${cfg.package.util-linux}/bin/sulogin"
+
+        # so NSS can look up usernames
+        "${pkgs.glibc}/lib/libnss_files.so"
+      ];
+
+      targets.initrd.aliases = ["default.target"];
+      units =
+           mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.paths
+        // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
+        // mapAttrs' (n: v: nameValuePair "${n}.slice"   (sliceToUnit   n v)) cfg.slices
+        // mapAttrs' (n: v: nameValuePair "${n}.socket"  (socketToUnit  n v)) cfg.sockets
+        // mapAttrs' (n: v: nameValuePair "${n}.target"  (targetToUnit  n v)) cfg.targets
+        // mapAttrs' (n: v: nameValuePair "${n}.timer"   (timerToUnit   n v)) cfg.timers
+        // listToAttrs (map
+                     (v: let n = escapeSystemdPath v.where;
+                         in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
+        // listToAttrs (map
+                     (v: let n = escapeSystemdPath v.where;
+                         in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
+
+      services.emergency = mkIf (isBool cfg.emergencyAccess && cfg.emergencyAccess) {
+        environment.SYSTEMD_SULOGIN_FORCE = "1";
+      };
+      # The unit in /run/systemd/generator shadows the unit in
+      # /etc/systemd/system, but will still apply drop-ins from
+      # /etc/systemd/system/foo.service.d/
+      #
+      # We need IgnoreOnIsolate, otherwise the Requires dependency of
+      # a mount unit on its makefs unit causes it to be unmounted when
+      # we isolate for switch-root. Use a dummy package so that
+      # generateUnits will generate drop-ins instead of unit files.
+      packages = [(pkgs.runCommand "dummy" {} ''
+        mkdir -p $out/etc/systemd/system
+        touch $out/etc/systemd/system/systemd-{makefs,growfs}@.service
+      '')];
+      services."systemd-makefs@".unitConfig.IgnoreOnIsolate = true;
+      services."systemd-growfs@".unitConfig.IgnoreOnIsolate = true;
+    };
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/nspawn.nix b/nixos/modules/system/boot/systemd/nspawn.nix
index 0c6822319a5b0..bf9995d03cc18 100644
--- a/nixos/modules/system/boot/systemd/nspawn.nix
+++ b/nixos/modules/system/boot/systemd/nspawn.nix
@@ -116,7 +116,13 @@ in {
     in
       mkMerge [
         (mkIf (cfg != {}) {
-          environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits' false "nspawn" units [] []);
+          environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits {
+            allowCollisions = false;
+            type = "nspawn";
+            inherit units;
+            upstreamUnits = [];
+            upstreamWants = [];
+          });
         })
         {
           systemd.targets.multi-user.wants = [ "machines.target" ];
diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix
index 57d44c8591ed1..edbe59965560f 100644
--- a/nixos/modules/system/boot/systemd/tmpfiles.nix
+++ b/nixos/modules/system/boot/systemd/tmpfiles.nix
@@ -100,5 +100,22 @@ in
         '';
       })
     ];
+
+    systemd.tmpfiles.rules = [
+      "d  /etc/nixos                         0755 root root - -"
+      "d  /nix/var                           0755 root root - -"
+      "L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system"
+      "d  /run/lock                          0755 root root - -"
+      "d  /var/db                            0755 root root - -"
+      "L  /etc/mtab                          -    -    -    - ../proc/mounts"
+      "L  /var/lock                          -    -    -    - ../run/lock"
+      # Boot-time cleanup
+      "R! /etc/group.lock                    -    -    -    - -"
+      "R! /etc/passwd.lock                   -    -    -    - -"
+      "R! /etc/shadow.lock                   -    -    -    - -"
+      "R! /etc/mtab*                         -    -    -    - -"
+      "R! /nix/var/nix/gcroots/tmp           -    -    -    - -"
+      "R! /nix/var/nix/temproots             -    -    -    - -"
+    ];
   };
 }
diff --git a/nixos/modules/system/boot/systemd/user.nix b/nixos/modules/system/boot/systemd/user.nix
index e30f83f3457f8..4951aef95584b 100644
--- a/nixos/modules/system/boot/systemd/user.nix
+++ b/nixos/modules/system/boot/systemd/user.nix
@@ -12,10 +12,6 @@ let
     (systemdUtils.lib)
     makeUnit
     generateUnits
-    makeJobScript
-    unitConfig
-    serviceConfig
-    commonUnitText
     targetToUnit
     serviceToUnit
     socketToUnit
@@ -57,48 +53,42 @@ in {
     systemd.user.units = mkOption {
       description = "Definition of systemd per-user units.";
       default = {};
-      type = with types; attrsOf (submodule (
-        { name, config, ... }:
-        { options = concreteUnitOptions;
-          config = {
-            unit = mkDefault (makeUnit name config);
-          };
-        }));
+      type = systemdUtils.types.units;
     };
 
     systemd.user.paths = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+      type = systemdUtils.types.paths;
       description = "Definition of systemd per-user path units.";
     };
 
     systemd.user.services = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] );
+      type = systemdUtils.types.services;
       description = "Definition of systemd per-user service units.";
     };
 
     systemd.user.slices = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] );
+      type = systemdUtils.types.slices;
       description = "Definition of systemd per-user slice units.";
     };
 
     systemd.user.sockets = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] );
+      type = systemdUtils.types.sockets;
       description = "Definition of systemd per-user socket units.";
     };
 
     systemd.user.targets = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+      type = systemdUtils.types.targets;
       description = "Definition of systemd per-user target units.";
     };
 
     systemd.user.timers = mkOption {
       default = {};
-      type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] );
+      type = systemdUtils.types.timers;
       description = "Definition of systemd per-user timer units.";
     };
 
@@ -119,7 +109,12 @@ in {
     ];
 
     environment.etc = {
-      "systemd/user".source = generateUnits "user" cfg.units upstreamUserUnits [];
+      "systemd/user".source = generateUnits {
+        type = "user";
+        inherit (cfg) units;
+        upstreamUnits = upstreamUserUnits;
+        upstreamWants = [];
+      };
 
       "systemd/user.conf".text = ''
         [Manager]
diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index 1404dcbaf7c0f..a5755d08d7de9 100644
--- a/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -90,7 +90,7 @@ in {
         example = "45min";
         description = ''
           Add a randomized delay before each automatic upgrade.
-          The delay will be chozen between zero and this value.
+          The delay will be chosen between zero and this value.
           This value must be a time span in the format specified by
           <citerefentry><refentrytitle>systemd.time</refentrytitle>
           <manvolnum>7</manvolnum></citerefentry>
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index f3da6771197e6..b8afe231dd2e1 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -215,6 +215,35 @@ in
       '';
     };
 
+    boot.devSize = mkOption {
+      default = "5%";
+      example = "32m";
+      type = types.str;
+      description = ''
+        Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
+        for the accepted syntax.
+      '';
+    };
+
+    boot.devShmSize = mkOption {
+      default = "50%";
+      example = "256m";
+      type = types.str;
+      description = ''
+        Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
+        for the accepted syntax.
+      '';
+    };
+
+    boot.runSize = mkOption {
+      default = "25%";
+      example = "256m";
+      type = types.str;
+      description = ''
+        Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
+        for the accepted syntax.
+      '';
+    };
   };
 
 
@@ -323,7 +352,7 @@ in
             unitConfig.DefaultDependencies = false; # needed to prevent a cycle
             serviceConfig.Type = "oneshot";
           };
-      in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems)) // {
+      in listToAttrs (map formatDevice (filter (fs: fs.autoFormat && !(utils.fsNeededForBoot fs)) fileSystems)) // {
     # Mount /sys/fs/pstore for evacuating panic logs and crashdumps from persistent storage onto the disk using systemd-pstore.
     # This cannot be done with the other special filesystems because the pstore module (which creates the mount point) is not loaded then.
         "mount-pstore" = {
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 60b5a48b2e62e..8ca4ad7b7d86d 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -1451,7 +1451,7 @@ in
           sysctl-value = tempaddrValues.${cfg.tempAddresses}.sysctl;
         in ''
           # enable and prefer IPv6 privacy addresses by default
-          ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
+          ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/$name/use_tempaddr'"
         '';
       })
       (pkgs.writeTextFile rec {
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index bd8c7f8c1eea3..e2425b44eac4a 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -146,15 +146,11 @@ in
 
     services.logrotate = {
       enable = true;
-      extraConfig = ''
-        /var/log/waagent.log {
-            compress
-            monthly
-            rotate 6
-            notifempty
-            missingok
-        }
-      '';
+      settings."/var/log/waagent.log" = {
+        compress = true;
+        frequency = "monthly";
+        rotate = 6;
+      };
     };
 
     systemd.targets.provisioned = {
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index 94fd727a4b564..b7e7f78ded78f 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -6,7 +6,10 @@ let
 
   inherit (lib) mkOption types;
 
-  podmanPackage = (pkgs.podman.override { inherit (cfg) extraPackages; });
+  podmanPackage = (pkgs.podman.override {
+    extraPackages = cfg.extraPackages
+      ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
+  });
 
   # Provides a fake "docker" binary mapping to podman
   dockerCompat = pkgs.runCommand "${podmanPackage.pname}-docker-compat-${podmanPackage.version}" {
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index dacbb64a2dacb..74f6521462b85 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -923,6 +923,8 @@ in
       mkVMOverride (cfg.fileSystems //
       {
         "/".device = cfg.bootDevice;
+        "/".fsType = "ext4";
+        "/".autoFormat = true;
 
         "/tmp" = mkIf config.boot.tmpOnTmpfs
           { device = "tmpfs";
@@ -953,6 +955,28 @@ in
           };
       } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
 
+    boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) {
+      mounts = [{
+        where = "/sysroot/nix/store";
+        what = "overlay";
+        type = "overlay";
+        options = "lowerdir=/sysroot/nix/.ro-store,upperdir=/sysroot/nix/.rw-store/store,workdir=/sysroot/nix/.rw-store/work";
+        wantedBy = ["local-fs.target"];
+        before = ["local-fs.target"];
+        requires = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
+        after = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
+        unitConfig.IgnoreOnIsolate = true;
+      }];
+      services.rw-store = {
+        after = ["sysroot-nix-.rw\\x2dstore.mount"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "/bin/mkdir -p 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
+        };
+      };
+    };
+
     swapDevices = mkVMOverride [ ];
     boot.initrd.luks.devices = mkVMOverride {};
 
@@ -961,7 +985,10 @@ in
 
     services.qemuGuest.enable = cfg.qemu.guestAgent.enable;
 
-    system.build.vm = pkgs.runCommand "nixos-vm" { preferLocalBuild = true; }
+    system.build.vm = pkgs.runCommand "nixos-vm" {
+      preferLocalBuild = true;
+      meta.mainProgram = "run-${config.system.name}-vm";
+    }
       ''
         mkdir -p $out/bin
         ln -s ${config.system.build.toplevel} $out/system
diff --git a/nixos/tests/aesmd.nix b/nixos/tests/aesmd.nix
index 59c04fe7e96a3..9f07426be8d8e 100644
--- a/nixos/tests/aesmd.nix
+++ b/nixos/tests/aesmd.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ veehaitch ];
   };
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     services.aesmd = {
       enable = true;
       settings = {
diff --git a/nixos/tests/agda.nix b/nixos/tests/agda.nix
index ec61af2afe754..6f51300111acf 100644
--- a/nixos/tests/agda.nix
+++ b/nixos/tests/agda.nix
@@ -15,7 +15,7 @@ in
     maintainers = [ alexarice turion ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     environment.systemPackages = [
       (pkgs.agda.withPackages {
         pkgs = p: [ p.standard-library ];
diff --git a/nixos/tests/airsonic.nix b/nixos/tests/airsonic.nix
index d8df092c2ecfa..2f60c56f643be 100644
--- a/nixos/tests/airsonic.nix
+++ b/nixos/tests/airsonic.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ sumnerevans ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
     {
       services.airsonic = {
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index ab6906cd24e20..dcbdf34e9441c 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -281,6 +281,7 @@ in
   #logstash = handleTest ./logstash.nix {};
   lorri = handleTest ./lorri/default.nix {};
   maddy = handleTest ./maddy.nix {};
+  maestral = handleTest ./maestral.nix {};
   magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
   magnetico = handleTest ./magnetico.nix {};
   mailcatcher = handleTest ./mailcatcher.nix {};
@@ -315,6 +316,7 @@ in
   moosefs = handleTest ./moosefs.nix {};
   mpd = handleTest ./mpd.nix {};
   mpv = handleTest ./mpv.nix {};
+  mtp = handleTest ./mtp.nix {};
   mumble = handleTest ./mumble.nix {};
   musescore = handleTest ./musescore.nix {};
   munin = handleTest ./munin.nix {};
@@ -341,6 +343,7 @@ in
   networking.networkd = handleTest ./networking.nix { networkd = true; };
   networking.scripted = handleTest ./networking.nix { networkd = false; };
   specialisation = handleTest ./specialisation.nix {};
+  netbox = handleTest ./web-apps/netbox.nix {};
   # TODO: put in networking.nix after the test becomes more complete
   networkingProxy = handleTest ./networking-proxy.nix {};
   nextcloud = handleTest ./nextcloud {};
@@ -511,6 +514,7 @@ in
   systemd-confinement = handleTest ./systemd-confinement.nix {};
   systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
   systemd-escaping = handleTest ./systemd-escaping.nix {};
+  systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {};
   systemd-journal = handleTest ./systemd-journal.nix {};
   systemd-machinectl = handleTest ./systemd-machinectl.nix {};
   systemd-networkd = handleTest ./systemd-networkd.nix {};
@@ -590,5 +594,6 @@ in
   zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
   zoneminder = handleTest ./zoneminder.nix {};
   zookeeper = handleTest ./zookeeper.nix {};
+  zrepl = handleTest ./zrepl.nix {};
   zsh-history = handleTest ./zsh-history.nix {};
 }
diff --git a/nixos/tests/amazon-init-shell.nix b/nixos/tests/amazon-init-shell.nix
index f9268b2f3a009..3c040841b6d29 100644
--- a/nixos/tests/amazon-init-shell.nix
+++ b/nixos/tests/amazon-init-shell.nix
@@ -18,7 +18,7 @@ makeTest {
   meta = with maintainers; {
     maintainers = [ urbas ];
   };
-  machine = { ... }:
+  nodes.machine = { ... }:
   {
     imports = [ ../modules/profiles/headless.nix ../modules/virtualisation/amazon-init.nix ];
     services.openssh.enable = true;
diff --git a/nixos/tests/apfs.nix b/nixos/tests/apfs.nix
index a82886cbe7317..a8841fe93046e 100644
--- a/nixos/tests/apfs.nix
+++ b/nixos/tests/apfs.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "apfs";
   meta.maintainers = with pkgs.lib.maintainers; [ Luflosi ];
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     virtualisation.emptyDiskImages = [ 1024 ];
 
     boot.supportedFilesystems = [ "apfs" ];
diff --git a/nixos/tests/apparmor.nix b/nixos/tests/apparmor.nix
index c6daa8e67de3f..f85bff0295e71 100644
--- a/nixos/tests/apparmor.nix
+++ b/nixos/tests/apparmor.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... } : {
     maintainers = [ julm ];
   };
 
-  machine =
+  nodes.machine =
     { lib, pkgs, config, ... }:
     with lib;
     {
diff --git a/nixos/tests/atd.nix b/nixos/tests/atd.nix
index ad4d60067cf1e..4342e9d7dc181 100644
--- a/nixos/tests/atd.nix
+++ b/nixos/tests/atd.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     maintainers = [ bjornfor ];
   };
 
-  machine =
+  nodes.machine =
     { ... }:
     { services.atd.enable = true;
       users.users.alice = { isNormalUser = true; };
diff --git a/nixos/tests/atop.nix b/nixos/tests/atop.nix
index f7a90346f3d74..d9304834692c8 100644
--- a/nixos/tests/atop.nix
+++ b/nixos/tests/atop.nix
@@ -107,7 +107,7 @@ in
 {
   justThePackage = makeTest {
     name = "atop-justThePackage";
-    machine = {
+    nodes.machine = {
       environment.systemPackages = [ pkgs.atop ];
     };
     testScript = with assertions; builtins.concatStringsSep "\n" [
@@ -123,7 +123,7 @@ in
   };
   defaults = makeTest {
     name = "atop-defaults";
-    machine = {
+    nodes.machine = {
       programs.atop = {
         enable = true;
       };
@@ -141,7 +141,7 @@ in
   };
   minimal = makeTest {
     name = "atop-minimal";
-    machine = {
+    nodes.machine = {
       programs.atop = {
         enable = true;
         atopService.enable = false;
@@ -162,7 +162,7 @@ in
   };
   netatop = makeTest {
     name = "atop-netatop";
-    machine = {
+    nodes.machine = {
       programs.atop = {
         enable = true;
         netatop.enable = true;
@@ -181,7 +181,7 @@ in
   };
   atopgpu = makeTest {
     name = "atop-atopgpu";
-    machine = {
+    nodes.machine = {
       nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (getName pkg) [
         "cudatoolkit"
       ];
@@ -204,7 +204,7 @@ in
   };
   everything = makeTest {
     name = "atop-everthing";
-    machine = {
+    nodes.machine = {
       nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (getName pkg) [
         "cudatoolkit"
       ];
diff --git a/nixos/tests/bcachefs.nix b/nixos/tests/bcachefs.nix
index 44997a746879b..832cc9c38f012 100644
--- a/nixos/tests/bcachefs.nix
+++ b/nixos/tests/bcachefs.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "bcachefs";
   meta.maintainers = with pkgs.lib.maintainers; [ chiiruno ];
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     virtualisation.emptyDiskImages = [ 4096 ];
     networking.hostId = "deadbeef";
     boot.supportedFilesystems = [ "bcachefs" ];
diff --git a/nixos/tests/beanstalkd.nix b/nixos/tests/beanstalkd.nix
index 4f4a454fb47f9..518f018408ad8 100644
--- a/nixos/tests/beanstalkd.nix
+++ b/nixos/tests/beanstalkd.nix
@@ -28,7 +28,7 @@ in
   name = "beanstalkd";
   meta.maintainers = [ lib.maintainers.aanderse ];
 
-  machine =
+  nodes.machine =
     { ... }:
     { services.beanstalkd.enable = true;
     };
diff --git a/nixos/tests/bees.nix b/nixos/tests/bees.nix
index 58a9c29513567..3ab9f38ada8fe 100644
--- a/nixos/tests/bees.nix
+++ b/nixos/tests/bees.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
 {
   name = "bees";
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     boot.initrd.postDeviceCommands = ''
       ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux1 /dev/vdb
       ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux2 /dev/vdc
diff --git a/nixos/tests/bind.nix b/nixos/tests/bind.nix
index 7234f56a1c3a8..15accbd49db43 100644
--- a/nixos/tests/bind.nix
+++ b/nixos/tests/bind.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix {
   name = "bind";
 
-  machine = { pkgs, lib, ... }: {
+  nodes.machine = { pkgs, lib, ... }: {
     services.bind.enable = true;
     services.bind.extraOptions = "empty-zones-enable no;";
     services.bind.zones = lib.singleton {
diff --git a/nixos/tests/bitcoind.nix b/nixos/tests/bitcoind.nix
index 3e9e085287ac0..04655b7f6a51b 100644
--- a/nixos/tests/bitcoind.nix
+++ b/nixos/tests/bitcoind.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = with maintainers; [ _1000101 ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.bitcoind."mainnet" = {
       enable = true;
       rpc = {
diff --git a/nixos/tests/blockbook-frontend.nix b/nixos/tests/blockbook-frontend.nix
index e17a2d0577977..dca4f2f53cc10 100644
--- a/nixos/tests/blockbook-frontend.nix
+++ b/nixos/tests/blockbook-frontend.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = with maintainers; [ _1000101 ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.blockbook-frontend."test" = {
       enable = true;
     };
diff --git a/nixos/tests/boot-stage1.nix b/nixos/tests/boot-stage1.nix
index 756decd2039d6..fbe82d61afaea 100644
--- a/nixos/tests/boot-stage1.nix
+++ b/nixos/tests/boot-stage1.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "boot-stage1";
 
-  machine = { config, pkgs, lib, ... }: {
+  nodes.machine = { config, pkgs, lib, ... }: {
     boot.extraModulePackages = let
       compileKernelModule = name: source: pkgs.runCommandCC name rec {
         inherit source;
diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix
index ec2a9f6527c93..8e469a6549728 100644
--- a/nixos/tests/boot.nix
+++ b/nixos/tests/boot.nix
@@ -42,7 +42,7 @@ let
         nodes = { };
         testScript =
           ''
-            machine = create_machine(${machineConfig})
+            nodes.machine = create_machine(${machineConfig})
             machine.start()
             machine.wait_for_unit("multi-user.target")
             machine.succeed("nix store verify --no-trust -r --option experimental-features nix-command /run/current-system")
@@ -83,7 +83,7 @@ let
         name = "boot-netboot-" + name;
         nodes = { };
         testScript = ''
-            machine = create_machine(${machineConfig})
+            nodes.machine = create_machine(${machineConfig})
             machine.start()
             machine.wait_for_unit("multi-user.target")
             machine.shutdown()
@@ -138,7 +138,7 @@ in {
         if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0:
             raise RuntimeError("Could not create mutable linked image")
 
-        machine = create_machine(${machineConfig})
+        nodes.machine = create_machine(${machineConfig})
         machine.start()
         machine.wait_for_unit("multi-user.target")
         machine.succeed("nix store verify -r --no-trust --option experimental-features nix-command /run/current-system")
diff --git a/nixos/tests/botamusique.nix b/nixos/tests/botamusique.nix
index ccb105dc142f2..ecb79cb69867e 100644
--- a/nixos/tests/botamusique.nix
+++ b/nixos/tests/botamusique.nix
@@ -6,6 +6,10 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
 
   nodes = {
     machine = { config, ... }: {
+      networking.extraHosts = ''
+        127.0.0.1 all.api.radio-browser.info
+      '';
+
       services.murmur = {
         enable = true;
         registerName = "NixOS tests";
diff --git a/nixos/tests/bpf.nix b/nixos/tests/bpf.nix
index e479cd0579215..5868e3bfcb4cc 100644
--- a/nixos/tests/bpf.nix
+++ b/nixos/tests/bpf.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "bpf";
   meta.maintainers = with pkgs.lib.maintainers; [ martinetd ];
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     programs.bcc.enable = true;
     environment.systemPackages = with pkgs; [ bpftrace ];
   };
diff --git a/nixos/tests/breitbandmessung.nix b/nixos/tests/breitbandmessung.nix
index 12b1a094839be..78df0d5017eb0 100644
--- a/nixos/tests/breitbandmessung.nix
+++ b/nixos/tests/breitbandmessung.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ lib, ... }: {
   name = "breitbandmessung";
   meta.maintainers = with lib.maintainers; [ b4dm4n ];
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [
       ./common/user-account.nix
       ./common/x11.nix
diff --git a/nixos/tests/brscan5.nix b/nixos/tests/brscan5.nix
index 9aed742f6de79..9156a4cccfcfa 100644
--- a/nixos/tests/brscan5.nix
+++ b/nixos/tests/brscan5.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ mattchrist ];
   };
 
-  machine = { pkgs, ... }:
+  nodes.machine = { pkgs, ... }:
     {
       nixpkgs.config.allowUnfree = true;
       hardware.sane = {
diff --git a/nixos/tests/buildkite-agents.nix b/nixos/tests/buildkite-agents.nix
index 6674a0e884ed3..2c5593323e873 100644
--- a/nixos/tests/buildkite-agents.nix
+++ b/nixos/tests/buildkite-agents.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     maintainers = [ flokli ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.buildkite-agents = {
       one = {
         privateSshKeyPath = (import ./ssh-keys.nix pkgs).snakeOilPrivateKey;
diff --git a/nixos/tests/cage.nix b/nixos/tests/cage.nix
index 83bae3deeeab2..39c8d0441b6d1 100644
--- a/nixos/tests/cage.nix
+++ b/nixos/tests/cage.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     maintainers = [ matthewbauer ];
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
 
   {
     imports = [ ./common/user-account.nix ];
diff --git a/nixos/tests/cagebreak.nix b/nixos/tests/cagebreak.nix
index c6c2c632b61ac..1dcc910f97444 100644
--- a/nixos/tests/cagebreak.nix
+++ b/nixos/tests/cagebreak.nix
@@ -13,7 +13,7 @@ in
     maintainers = [ berbiche ];
   };
 
-  machine = { config, ... }:
+  nodes.machine = { config, ... }:
   let
     alice = config.users.users.alice;
   in {
diff --git a/nixos/tests/cfssl.nix b/nixos/tests/cfssl.nix
index 170f09d9b76cc..e673df3131f8e 100644
--- a/nixos/tests/cfssl.nix
+++ b/nixos/tests/cfssl.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "cfssl";
 
-  machine = { config, lib, pkgs, ... }:
+  nodes.machine = { config, lib, pkgs, ... }:
   {
     networking.firewall.allowedTCPPorts = [ config.services.cfssl.port ];
 
diff --git a/nixos/tests/clickhouse.nix b/nixos/tests/clickhouse.nix
index 017f2ee35dab0..043263ec05dd9 100644
--- a/nixos/tests/clickhouse.nix
+++ b/nixos/tests/clickhouse.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "clickhouse";
   meta.maintainers = with pkgs.lib.maintainers; [ ma27 ];
 
-  machine = {
+  nodes.machine = {
     services.clickhouse.enable = true;
     virtualisation.memorySize = 4096;
   };
diff --git a/nixos/tests/cloud-init.nix b/nixos/tests/cloud-init.nix
index 3f191ff5616e0..9feb8d5c1526e 100644
--- a/nixos/tests/cloud-init.nix
+++ b/nixos/tests/cloud-init.nix
@@ -61,7 +61,7 @@ in makeTest {
   meta = with pkgs.lib.maintainers; {
     maintainers = [ lewo ];
   };
-  machine = { ... }:
+  nodes.machine = { ... }:
   {
     virtualisation.qemu.options = [ "-cdrom" "${metadataDrive}/metadata.iso" ];
     services.cloud-init = {
diff --git a/nixos/tests/cntr.nix b/nixos/tests/cntr.nix
index e4e13545b8762..598143beb6c0f 100644
--- a/nixos/tests/cntr.nix
+++ b/nixos/tests/cntr.nix
@@ -46,7 +46,7 @@ let
 
     meta = with pkgs.lib.maintainers; { maintainers = [ sorki mic92 ]; };
 
-    machine = { lib, ... }: {
+    nodes.machine = { lib, ... }: {
       environment.systemPackages = [ pkgs.cntr ];
       containers.test = {
         autoStart = true;
diff --git a/nixos/tests/collectd.nix b/nixos/tests/collectd.nix
index cb196224a2317..2480bdb5f917e 100644
--- a/nixos/tests/collectd.nix
+++ b/nixos/tests/collectd.nix
@@ -2,12 +2,15 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "collectd";
   meta = { };
 
-  machine =
-    { pkgs, ... }:
+  nodes.machine =
+    { pkgs, lib, ... }:
 
     {
       services.collectd = {
         enable = true;
+        extraConfig = lib.mkBefore ''
+          Interval 30
+        '';
         plugins = {
           rrdtool = ''
             DataDir "/var/lib/collectd/rrd"
@@ -26,6 +29,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     machine.succeed(f"rrdinfo {file} | logger")
     # check that this file contains a shortterm metric
     machine.succeed(f"rrdinfo {file} | grep -F 'ds[shortterm].min = '")
+    # check that interval was set before the plugins
+    machine.succeed(f"rrdinfo {file} | grep -F 'step = 30'")
     # check that there are frequent updates
     machine.succeed(f"cp {file} before")
     machine.wait_until_fails(f"cmp before {file}")
diff --git a/nixos/tests/containers-bridge.nix b/nixos/tests/containers-bridge.nix
index b8661fd7997c9..d2e16299edaad 100644
--- a/nixos/tests/containers-bridge.nix
+++ b/nixos/tests/containers-bridge.nix
@@ -11,7 +11,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
diff --git a/nixos/tests/containers-custom-pkgs.nix b/nixos/tests/containers-custom-pkgs.nix
index 1627a2c70c3ca..9894e6643762e 100644
--- a/nixos/tests/containers-custom-pkgs.nix
+++ b/nixos/tests/containers-custom-pkgs.nix
@@ -12,7 +12,7 @@ in {
     maintainers = with lib.maintainers; [ adisbladis earvstedt ];
   };
 
-  machine = { config, ... }: {
+  nodes.machine = { config, ... }: {
     assertions = let
       helloName = (builtins.head config.containers.test.config.system.extraDependencies).name;
     in [ {
diff --git a/nixos/tests/containers-ephemeral.nix b/nixos/tests/containers-ephemeral.nix
index db1631cf5b5d1..c9fe2778cacdf 100644
--- a/nixos/tests/containers-ephemeral.nix
+++ b/nixos/tests/containers-ephemeral.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ patryk27 ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     virtualisation.writableStore = true;
 
     containers.webserver = {
diff --git a/nixos/tests/containers-extra_veth.nix b/nixos/tests/containers-extra_veth.nix
index b8f3d9844064c..f3e62265f6c4f 100644
--- a/nixos/tests/containers-extra_veth.nix
+++ b/nixos/tests/containers-extra_veth.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ kampfschlaefer ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
diff --git a/nixos/tests/containers-hosts.nix b/nixos/tests/containers-hosts.nix
index 3c6a15710027a..7bce7c997efee 100644
--- a/nixos/tests/containers-hosts.nix
+++ b/nixos/tests/containers-hosts.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ montag451 ];
   };
 
-  machine =
+  nodes.machine =
     { lib, ... }:
     {
       virtualisation.vlans = [];
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 14001657bee0b..44d2e50288b4a 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
   };
 
-  machine =
+  nodes.machine =
     { config, pkgs, lib, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
 
diff --git a/nixos/tests/containers-ip.nix b/nixos/tests/containers-ip.nix
index 91fdda0392a9b..ecead5c22f756 100644
--- a/nixos/tests/containers-ip.nix
+++ b/nixos/tests/containers-ip.nix
@@ -17,7 +17,7 @@ in import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }: {
       imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation = {
diff --git a/nixos/tests/containers-names.nix b/nixos/tests/containers-names.nix
index 9ad2bfb748a8d..721f64990724e 100644
--- a/nixos/tests/containers-names.nix
+++ b/nixos/tests/containers-names.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ patryk27 ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     # We're using the newest kernel, so that we can test containers with long names.
     # Please see https://github.com/NixOS/nixpkgs/issues/38509 for details.
     boot.kernelPackages = pkgs.linuxPackages_latest;
diff --git a/nixos/tests/containers-nested.nix b/nixos/tests/containers-nested.nix
index a653361494f96..4a9fb8f01e241 100644
--- a/nixos/tests/containers-nested.nix
+++ b/nixos/tests/containers-nested.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   meta = with pkgs.lib.maintainers; { maintainers = [ sorki ]; };
 
-  machine = { lib, ... }:
+  nodes.machine = { lib, ... }:
     let
       makeNested = subConf: {
         containers.nested = {
diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix
index 6cecd72f1bda3..b8c7aabc5a50b 100644
--- a/nixos/tests/containers-portforward.nix
+++ b/nixos/tests/containers-portforward.nix
@@ -11,7 +11,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ianwookim ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
diff --git a/nixos/tests/containers-tmpfs.nix b/nixos/tests/containers-tmpfs.nix
index d95178d1ff588..7a2c835b120aa 100644
--- a/nixos/tests/containers-tmpfs.nix
+++ b/nixos/tests/containers-tmpfs.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ patryk27 ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
     { imports = [ ../modules/installer/cd-dvd/channel.nix ];
       virtualisation.writableStore = true;
diff --git a/nixos/tests/custom-ca.nix b/nixos/tests/custom-ca.nix
index a55449a397a7c..60c6c82223a93 100644
--- a/nixos/tests/custom-ca.nix
+++ b/nixos/tests/custom-ca.nix
@@ -76,7 +76,7 @@ in
 
   enableOCR = true;
 
-  machine = { pkgs, ... }:
+  nodes.machine = { pkgs, ... }:
     { imports = [ ./common/user-account.nix ./common/x11.nix ];
 
       # chromium-based browsers refuse to run as root
diff --git a/nixos/tests/disable-installer-tools.nix b/nixos/tests/disable-installer-tools.nix
index 23c15faa8d334..69f99122753a8 100644
--- a/nixos/tests/disable-installer-tools.nix
+++ b/nixos/tests/disable-installer-tools.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
 {
   name = "disable-installer-tools";
 
-  machine =
+  nodes.machine =
     { pkgs, lib, ... }:
     {
         system.disableInstallerTools = true;
diff --git a/nixos/tests/dnsdist.nix b/nixos/tests/dnsdist.nix
index cfc41c13864e0..e72fa05ff282b 100644
--- a/nixos/tests/dnsdist.nix
+++ b/nixos/tests/dnsdist.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix (
       maintainers = with maintainers; [ jojosch ];
     };
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       services.bind = {
         enable = true;
         extraOptions = "empty-zones-enable no;";
diff --git a/nixos/tests/doas.nix b/nixos/tests/doas.nix
index 7f038b2bee296..3713c728195ce 100644
--- a/nixos/tests/doas.nix
+++ b/nixos/tests/doas.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix (
       maintainers = [ cole-h ];
     };
 
-    machine =
+    nodes.machine =
       { ... }:
         {
           users.groups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
diff --git a/nixos/tests/documize.nix b/nixos/tests/documize.nix
index d5a77ffcd4f26..528bf5338ce0d 100644
--- a/nixos/tests/documize.nix
+++ b/nixos/tests/documize.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     maintainers = [ ma27 ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     environment.systemPackages = [ pkgs.jq ];
 
     services.documize = {
diff --git a/nixos/tests/domination.nix b/nixos/tests/domination.nix
index c76d4ed8c61b3..09027740ab8de 100644
--- a/nixos/tests/domination.nix
+++ b/nixos/tests/domination.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ fgaz ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
diff --git a/nixos/tests/dovecot.nix b/nixos/tests/dovecot.nix
index 8913c2a6a7e82..5439387807fd7 100644
--- a/nixos/tests/dovecot.nix
+++ b/nixos/tests/dovecot.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix {
   name = "dovecot";
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ common/user-account.nix ];
     services.postfix.enable = true;
     services.dovecot2 = {
diff --git a/nixos/tests/ecryptfs.nix b/nixos/tests/ecryptfs.nix
index ef7bd13eb92c4..e3cfb2ed998c2 100644
--- a/nixos/tests/ecryptfs.nix
+++ b/nixos/tests/ecryptfs.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ ... }:
 {
   name = "ecryptfs";
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ ./common/user-account.nix ];
     boot.kernelModules = [ "ecryptfs" ];
     security.pam.enableEcryptfs = true;
diff --git a/nixos/tests/emacs-daemon.nix b/nixos/tests/emacs-daemon.nix
index e12da56021dab..d53031a67f62c 100644
--- a/nixos/tests/emacs-daemon.nix
+++ b/nixos/tests/emacs-daemon.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
   enableOCR = true;
 
-  machine =
+  nodes.machine =
     { ... }:
 
     { imports = [ ./common/x11.nix ];
diff --git a/nixos/tests/enlightenment.nix b/nixos/tests/enlightenment.nix
index 8506c348246de..2e06eedd9915b 100644
--- a/nixos/tests/enlightenment.nix
+++ b/nixos/tests/enlightenment.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     maintainers = [ romildo ];
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
   {
     imports = [ ./common/user-account.nix ];
     services.xserver.enable = true;
diff --git a/nixos/tests/env.nix b/nixos/tests/env.nix
index fc96ace6b2d2a..dec17b6b565a0 100644
--- a/nixos/tests/env.nix
+++ b/nixos/tests/env.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ nequissimus ];
   };
 
-  machine = { pkgs, ... }:
+  nodes.machine = { pkgs, ... }:
     {
       boot.kernelPackages = pkgs.linuxPackages;
       environment.etc.plainFile.text = ''
diff --git a/nixos/tests/etebase-server.nix b/nixos/tests/etebase-server.nix
index 4fc3c1f6392f8..49bfccf359e2a 100644
--- a/nixos/tests/etebase-server.nix
+++ b/nixos/tests/etebase-server.nix
@@ -9,7 +9,7 @@ in {
       maintainers = [ felschr ];
     };
 
-    machine = { pkgs, ... }:
+    nodes.machine = { pkgs, ... }:
       {
         services.etebase-server = {
           inherit dataDir;
diff --git a/nixos/tests/etesync-dav.nix b/nixos/tests/etesync-dav.nix
index 6a747e23f76f6..f49152c60991f 100644
--- a/nixos/tests/etesync-dav.nix
+++ b/nixos/tests/etesync-dav.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ _3699n ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
       environment.systemPackages = [ pkgs.curl pkgs.etesync-dav ];
   };
 
diff --git a/nixos/tests/fancontrol.nix b/nixos/tests/fancontrol.nix
index 296c680264150..ecb9360974463 100644
--- a/nixos/tests/fancontrol.nix
+++ b/nixos/tests/fancontrol.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... } : {
     maintainers = [ evils ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
     hardware.fancontrol.enable = true;
     hardware.fancontrol.config = ''
diff --git a/nixos/tests/fcitx/default.nix b/nixos/tests/fcitx/default.nix
index a243be8dc19b1..78b322d351d3b 100644
--- a/nixos/tests/fcitx/default.nix
+++ b/nixos/tests/fcitx/default.nix
@@ -5,7 +5,7 @@ import ../make-test-python.nix (
     # copy_from_host works only for store paths
     rec {
         name = "fcitx";
-        machine =
+        nodes.machine =
         {
           pkgs,
           ...
diff --git a/nixos/tests/firefox.nix b/nixos/tests/firefox.nix
index 6101fc9735641..c773368a3e60a 100644
--- a/nixos/tests/firefox.nix
+++ b/nixos/tests/firefox.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
     maintainers = [ eelco shlevy ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
 
     { imports = [ ./common/x11.nix ];
diff --git a/nixos/tests/fish.nix b/nixos/tests/fish.nix
index 68fba428439b6..3d9b13c6af70a 100644
--- a/nixos/tests/fish.nix
+++ b/nixos/tests/fish.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "fish";
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
 
     {
diff --git a/nixos/tests/fluentd.nix b/nixos/tests/fluentd.nix
index 918f2f87db17f..150638f246f26 100644
--- a/nixos/tests/fluentd.nix
+++ b/nixos/tests/fluentd.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "fluentd";
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.fluentd = {
       enable = true;
       config = ''
diff --git a/nixos/tests/fontconfig-default-fonts.nix b/nixos/tests/fontconfig-default-fonts.nix
index 58d0f6227cc7b..664afc9bf44c4 100644
--- a/nixos/tests/fontconfig-default-fonts.nix
+++ b/nixos/tests/fontconfig-default-fonts.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ lib, ... }:
     jtojnar
   ];
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     fonts.enableDefaultFonts = true; # Background fonts
     fonts.fonts = with pkgs; [
       noto-fonts-emoji
diff --git a/nixos/tests/fsck.nix b/nixos/tests/fsck.nix
index 5453f3bc48b59..5b8b09f433a22 100644
--- a/nixos/tests/fsck.nix
+++ b/nixos/tests/fsck.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix {
   name = "fsck";
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     virtualisation.emptyDiskImages = [ 1 ];
 
     virtualisation.fileSystems = {
diff --git a/nixos/tests/ft2-clone.nix b/nixos/tests/ft2-clone.nix
index 71eda43e2b245..3c90b3d3fa201 100644
--- a/nixos/tests/ft2-clone.nix
+++ b/nixos/tests/ft2-clone.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ fgaz ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
diff --git a/nixos/tests/geth.nix b/nixos/tests/geth.nix
index af8230553bbbb..11ad1ed2ea66f 100644
--- a/nixos/tests/geth.nix
+++ b/nixos/tests/geth.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = with maintainers; [bachp ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.geth."mainnet" = {
       enable = true;
       http = {
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index dc3b889c8e8e2..e1916ed36f315 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -6,7 +6,7 @@ in
 import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
   name = "gitlab";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ globin ];
+    maintainers = [ globin yayayayaka ];
   };
 
   nodes = {
@@ -112,21 +112,27 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : with lib; {
             "${pkgs.sudo}/bin/sudo -u gitlab -H gitlab-rake gitlab:check 1>&2"
         )
         gitlab.succeed(
-            "echo \"Authorization: Bearer \$(curl -X POST -H 'Content-Type: application/json' -d @${auth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers"
+            "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${auth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers"
         )
       '' + optionalString doSetup ''
         gitlab.succeed(
-            "curl -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createProject} http://gitlab/api/v4/projects"
+            """[ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createProject} http://gitlab/api/v4/projects)" = "201" ]"""
         )
         gitlab.succeed(
-            "curl -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${putFile} http://gitlab/api/v4/projects/1/repository/files/some-file.txt"
+            """[ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${putFile} http://gitlab/api/v4/projects/2/repository/files/some-file.txt)" = "201" ]"""
         )
       '' + ''
         gitlab.succeed(
-            "curl -H @/tmp/headers http://gitlab/api/v4/projects/1/repository/archive.tar.gz > /tmp/archive.tar.gz"
+            """[ "$(curl -o /dev/null -w '%{http_code}' -H @/tmp/headers http://gitlab/api/v4/projects/2/repository/archive.tar.gz)" = "200" ]"""
         )
         gitlab.succeed(
-            "curl -H @/tmp/headers http://gitlab/api/v4/projects/1/repository/archive.tar.bz2 > /tmp/archive.tar.bz2"
+            """curl -H @/tmp/headers http://gitlab/api/v4/projects/2/repository/archive.tar.gz > /tmp/archive.tar.gz"""
+        )
+        gitlab.succeed(
+            """[ "$(curl -o /dev/null -w '%{http_code}' -H @/tmp/headers http://gitlab/api/v4/projects/2/repository/archive.tar.bz2)" = "200" ]"""
+        )
+        gitlab.succeed(
+            """curl -o /dev/null -w '%{http_code}' -H @/tmp/headers http://gitlab/api/v4/projects/2/repository/archive.tar.bz2 > /tmp/archive.tar.bz2"""
         )
         gitlab.succeed("test -s /tmp/archive.tar.gz")
         gitlab.succeed("test -s /tmp/archive.tar.bz2")
diff --git a/nixos/tests/gnome-xorg.nix b/nixos/tests/gnome-xorg.nix
index d7be531e364ec..618458b1f6b5b 100644
--- a/nixos/tests/gnome-xorg.nix
+++ b/nixos/tests/gnome-xorg.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     maintainers = teams.gnome.members;
   };
 
-  machine = { nodes, ... }: let
+  nodes.machine = { nodes, ... }: let
     user = nodes.machine.config.users.users.alice;
   in
 
diff --git a/nixos/tests/gnome.nix b/nixos/tests/gnome.nix
index ca49183fe442c..05619cbd7d82a 100644
--- a/nixos/tests/gnome.nix
+++ b/nixos/tests/gnome.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     maintainers = teams.gnome.members;
   };
 
-  machine =
+  nodes.machine =
     { ... }:
 
     { imports = [ ./common/user-account.nix ];
diff --git a/nixos/tests/gotify-server.nix b/nixos/tests/gotify-server.nix
index 051666fbe72e7..e7942b76d8e50 100644
--- a/nixos/tests/gotify-server.nix
+++ b/nixos/tests/gotify-server.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     maintainers = [ ma27 ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     environment.systemPackages = [ pkgs.jq ];
 
     services.gotify = {
diff --git a/nixos/tests/graylog.nix b/nixos/tests/graylog.nix
index 572904f60d575..23f426fc7af95 100644
--- a/nixos/tests/graylog.nix
+++ b/nixos/tests/graylog.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "graylog";
   meta.maintainers = with lib.maintainers; [ ];
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     virtualisation.memorySize = 4096;
     virtualisation.diskSize = 4096;
 
diff --git a/nixos/tests/grocy.nix b/nixos/tests/grocy.nix
index 2be5c24ecb55c..fe0ddd341486b 100644
--- a/nixos/tests/grocy.nix
+++ b/nixos/tests/grocy.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ ma27 ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.grocy = {
       enable = true;
       hostName = "localhost";
diff --git a/nixos/tests/grub.nix b/nixos/tests/grub.nix
index 84bfc90955b54..e0875e70f6a51 100644
--- a/nixos/tests/grub.nix
+++ b/nixos/tests/grub.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ lib, ... }: {
     maintainers = [ rnhmjoj ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     virtualisation.useBootLoader = true;
 
     boot.loader.timeout = null;
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
index dc455f971f5c1..3afa8ebf2b5f7 100644
--- a/nixos/tests/hardened.nix
+++ b/nixos/tests/hardened.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... } : {
     maintainers = [ joachifm ];
   };
 
-  machine =
+  nodes.machine =
     { lib, pkgs, config, ... }:
     with lib;
     { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
diff --git a/nixos/tests/herbstluftwm.nix b/nixos/tests/herbstluftwm.nix
index 7d079f4bfb695..b6965914360e7 100644
--- a/nixos/tests/herbstluftwm.nix
+++ b/nixos/tests/herbstluftwm.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ lib, ...} : {
     maintainers = with lib.maintainers; [ thibautmarty ];
   };
 
-  machine = { pkgs, lib, ... }: {
+  nodes.machine = { pkgs, lib, ... }: {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
     test-support.displayManager.auto.user = "alice";
     services.xserver.displayManager.defaultSession = lib.mkForce "none+herbstluftwm";
diff --git a/nixos/tests/hibernate.nix b/nixos/tests/hibernate.nix
index 3880f1649bd3d..f81033b846aef 100644
--- a/nixos/tests/hibernate.nix
+++ b/nixos/tests/hibernate.nix
@@ -39,7 +39,7 @@ in makeTest {
 
   nodes = {
     # System configuration used for installing the installedConfig from above.
-    machine = { config, lib, pkgs, ... }: with lib; {
+    nodes.machine = { config, lib, pkgs, ... }: with lib; {
       imports = [
         ../modules/profiles/installation-device.nix
         ../modules/profiles/base.nix
@@ -117,6 +117,11 @@ in makeTest {
       resume = create_named_machine("resume")
       resume.start()
       resume.succeed("grep 'not persisted to disk' /run/test/suspended")
+
+      # Ensure we don't restore from hibernation when booting again
+      resume.crash()
+      resume.wait_for_unit("default.target")
+      resume.fail("grep 'not persisted to disk' /run/test/suspended")
     '';
 
 }
diff --git a/nixos/tests/hitch/default.nix b/nixos/tests/hitch/default.nix
index a1d8e6162606d..4283b9f7dffbe 100644
--- a/nixos/tests/hitch/default.nix
+++ b/nixos/tests/hitch/default.nix
@@ -4,7 +4,7 @@ import ../make-test-python.nix ({ pkgs, ... }:
   meta = with pkgs.lib.maintainers; {
     maintainers = [ jflanglois ];
   };
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     environment.systemPackages = [ pkgs.curl ];
     services.hitch = {
       enable = true;
diff --git a/nixos/tests/hocker-fetchdocker/default.nix b/nixos/tests/hocker-fetchdocker/default.nix
index e3979db3c60b5..b5c06126c2e80 100644
--- a/nixos/tests/hocker-fetchdocker/default.nix
+++ b/nixos/tests/hocker-fetchdocker/default.nix
@@ -5,7 +5,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     broken = true; # tries to download from registry-1.docker.io - how did this ever work?
   };
 
-  machine = import ./machine.nix;
+  nodes.machine = import ./machine.nix;
 
   testScript = ''
     start_all()
diff --git a/nixos/tests/hockeypuck.nix b/nixos/tests/hockeypuck.nix
index 19df9dee3d315..d1ef4cbf588a8 100644
--- a/nixos/tests/hockeypuck.nix
+++ b/nixos/tests/hockeypuck.nix
@@ -24,7 +24,7 @@ in {
   name = "hockeypuck";
   meta.maintainers = with lib.maintainers; [ etu ];
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     # Used for test
     environment.systemPackages = [ pkgs.gnupg ];
 
diff --git a/nixos/tests/hostname.nix b/nixos/tests/hostname.nix
index 2e92b4259a6ab..1de8f19267af8 100644
--- a/nixos/tests/hostname.nix
+++ b/nixos/tests/hostname.nix
@@ -20,7 +20,7 @@ let
           maintainers = [ primeos blitz ];
         };
 
-        machine = { lib, ... }: {
+        nodes.machine = { lib, ... }: {
           networking.hostName = hostName;
           networking.domain = domain;
 
diff --git a/nixos/tests/hound.nix b/nixos/tests/hound.nix
index 4f51db1de9dec..a9b036deb0dda 100644
--- a/nixos/tests/hound.nix
+++ b/nixos/tests/hound.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... } : {
   meta = with pkgs.lib.maintainers; {
     maintainers = [ grahamc ];
   };
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.hound = {
       enable = true;
       config = ''
diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix
index ef5e677953dcd..9fc787842d854 100644
--- a/nixos/tests/hydra/default.nix
+++ b/nixos/tests/hydra/default.nix
@@ -20,7 +20,7 @@ let
       maintainers = [ lewo ma27 ];
     };
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       imports = [ baseConfig ];
       services.hydra = { inherit package; };
     };
diff --git a/nixos/tests/i3wm.nix b/nixos/tests/i3wm.nix
index 59b4ffe3986e2..b216650d8192b 100644
--- a/nixos/tests/i3wm.nix
+++ b/nixos/tests/i3wm.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ aszlig ];
   };
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
     test-support.displayManager.auto.user = "alice";
     services.xserver.displayManager.defaultSession = lib.mkForce "none+i3";
diff --git a/nixos/tests/ihatemoney/default.nix b/nixos/tests/ihatemoney/default.nix
index 78278d2e86996..cd5f073343daa 100644
--- a/nixos/tests/ihatemoney/default.nix
+++ b/nixos/tests/ihatemoney/default.nix
@@ -7,7 +7,7 @@ let
   inherit (import ../../lib/testing-python.nix { inherit system pkgs; }) makeTest;
   f = backend: makeTest {
     name = "ihatemoney-${backend}";
-    machine = { nodes, lib, ... }: {
+    nodes.machine = { nodes, lib, ... }: {
       services.ihatemoney = {
         enable = true;
         enablePublicProjectCreation = true;
diff --git a/nixos/tests/incron.nix b/nixos/tests/incron.nix
index b22ee4c9a037a..c978ff27dfad5 100644
--- a/nixos/tests/incron.nix
+++ b/nixos/tests/incron.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
   name = "incron";
   meta.maintainers = [ lib.maintainers.aanderse ];
 
-  machine =
+  nodes.machine =
     { ... }:
     { services.incron.enable = true;
       services.incron.extraPackages = [ pkgs.coreutils ];
diff --git a/nixos/tests/initrd-network.nix b/nixos/tests/initrd-network.nix
index 14e7e7d40bc59..f2483b7393de4 100644
--- a/nixos/tests/initrd-network.nix
+++ b/nixos/tests/initrd-network.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
   meta.maintainers = [ pkgs.lib.maintainers.eelco ];
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
     boot.initrd.network.enable = true;
     boot.initrd.network.postCommands =
diff --git a/nixos/tests/initrd-secrets.nix b/nixos/tests/initrd-secrets.nix
index 113a9cebf7880..0f3f83b0904e3 100644
--- a/nixos/tests/initrd-secrets.nix
+++ b/nixos/tests/initrd-secrets.nix
@@ -11,7 +11,7 @@ let
 
     meta.maintainers = [ lib.maintainers.lheckemann ];
 
-    machine = { ... }: {
+    nodes.machine = { ... }: {
       virtualisation.useBootLoader = true;
       boot.initrd.secrets = {
         "/test" = secretInStore;
diff --git a/nixos/tests/input-remapper.nix b/nixos/tests/input-remapper.nix
index f692564caa577..1b0350063f7f2 100644
--- a/nixos/tests/input-remapper.nix
+++ b/nixos/tests/input-remapper.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       maintainers = with pkgs.lib.maintainers; [ LunNova ];
     };
 
-    machine = { config, ... }:
+    nodes.machine = { config, ... }:
       let user = config.users.users.sybil; in
       {
         imports = [
diff --git a/nixos/tests/installed-tests/default.nix b/nixos/tests/installed-tests/default.nix
index 079fd54e71e57..fd16b481168f4 100644
--- a/nixos/tests/installed-tests/default.nix
+++ b/nixos/tests/installed-tests/default.nix
@@ -43,7 +43,7 @@ let
             maintainers = tested.meta.maintainers;
           };
 
-          machine = { ... }: {
+          nodes.machine = { ... }: {
             imports = [
               testConfig
             ] ++ optional withX11 ../common/x11.nix;
diff --git a/nixos/tests/invidious.nix b/nixos/tests/invidious.nix
index 8b831715a441f..582d1550fff1a 100644
--- a/nixos/tests/invidious.nix
+++ b/nixos/tests/invidious.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ sbruder ];
   };
 
-  machine = { config, lib, pkgs, ... }: {
+  nodes.machine = { config, lib, pkgs, ... }: {
     services.invidious = {
       enable = true;
     };
diff --git a/nixos/tests/isso.nix b/nixos/tests/isso.nix
index 99dc8009ae064..65bae5f5dcede 100644
--- a/nixos/tests/isso.nix
+++ b/nixos/tests/isso.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ asbachb ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     services.isso = {
       enable = true;
       settings = {
diff --git a/nixos/tests/jellyfin.nix b/nixos/tests/jellyfin.nix
index cae31a7192582..4ac3786996378 100644
--- a/nixos/tests/jellyfin.nix
+++ b/nixos/tests/jellyfin.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
     name = "jellyfin";
     meta.maintainers = with lib.maintainers; [ minijackson ];
 
-    machine =
+    nodes.machine =
       { ... }:
       {
         services.jellyfin.enable = true;
diff --git a/nixos/tests/jenkins.nix b/nixos/tests/jenkins.nix
index cb4207c6e7738..265d1a330cd94 100644
--- a/nixos/tests/jenkins.nix
+++ b/nixos/tests/jenkins.nix
@@ -90,6 +90,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
 
     slave.fail("systemctl is-enabled jenkins.service")
 
+    slave.succeed("java -fullversion")
+
     with subtest("jobs are declarative"):
         # Check that jobs are created on disk.
         master.wait_for_unit("jenkins-job-builder")
diff --git a/nixos/tests/jibri.nix b/nixos/tests/jibri.nix
index af20e639d30ef..223120cdb2291 100644
--- a/nixos/tests/jibri.nix
+++ b/nixos/tests/jibri.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = teams.jitsi.members;
   };
 
-    machine = { config, pkgs, ... }: {
+    nodes.machine = { config, pkgs, ... }: {
       virtualisation.memorySize = 5120;
 
       services.jitsi-meet = {
diff --git a/nixos/tests/k3s-single-node-docker.nix b/nixos/tests/k3s-single-node-docker.nix
index 7f3d15788b043..735aa5ac2975d 100644
--- a/nixos/tests/k3s-single-node-docker.nix
+++ b/nixos/tests/k3s-single-node-docker.nix
@@ -38,7 +38,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       maintainers = [ euank ];
     };
 
-    machine = { pkgs, ... }: {
+    nodes.machine = { pkgs, ... }: {
       environment.systemPackages = with pkgs; [ k3s gzip ];
 
       # k3s uses enough resources the default vm fails.
diff --git a/nixos/tests/k3s-single-node.nix b/nixos/tests/k3s-single-node.nix
index d98f20d468cbc..fb6510ee087bc 100644
--- a/nixos/tests/k3s-single-node.nix
+++ b/nixos/tests/k3s-single-node.nix
@@ -38,7 +38,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       maintainers = [ euank ];
     };
 
-    machine = { pkgs, ... }: {
+    nodes.machine = { pkgs, ... }: {
       environment.systemPackages = with pkgs; [ k3s gzip ];
 
       # k3s uses enough resources the default vm fails.
diff --git a/nixos/tests/kbd-setfont-decompress.nix b/nixos/tests/kbd-setfont-decompress.nix
index c3a495afac840..810ef39cc11a3 100644
--- a/nixos/tests/kbd-setfont-decompress.nix
+++ b/nixos/tests/kbd-setfont-decompress.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
 
   meta.maintainers = with lib.maintainers; [ oxalica ];
 
-  machine = { ... }: {};
+  nodes.machine = { ... }: {};
 
   testScript = ''
     machine.succeed("gzip -cd ${pkgs.terminus_font}/share/consolefonts/ter-v16b.psf.gz >font.psf")
diff --git a/nixos/tests/kbd-update-search-paths-patch.nix b/nixos/tests/kbd-update-search-paths-patch.nix
index 2cdb12340b1bc..746a809c4cdf7 100644
--- a/nixos/tests/kbd-update-search-paths-patch.nix
+++ b/nixos/tests/kbd-update-search-paths-patch.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "kbd-update-search-paths-patch";
 
-  machine = { pkgs, options, ... }: {
+  nodes.machine = { pkgs, options, ... }: {
     console = {
       packages = options.console.packages.default ++ [ pkgs.terminus_font ];
     };
diff --git a/nixos/tests/keepassxc.nix b/nixos/tests/keepassxc.nix
index 924c137a9032d..d0f353c71e0ba 100644
--- a/nixos/tests/keepassxc.nix
+++ b/nixos/tests/keepassxc.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     maintainers = [ turion ];
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
 
   {
     imports = [
diff --git a/nixos/tests/kerberos/heimdal.nix b/nixos/tests/kerberos/heimdal.nix
index 391a61cc9a90b..47f9d0285aef7 100644
--- a/nixos/tests/kerberos/heimdal.nix
+++ b/nixos/tests/kerberos/heimdal.nix
@@ -1,6 +1,6 @@
 import ../make-test-python.nix ({pkgs, ...}: {
   name = "kerberos_server-heimdal";
-  machine = { config, libs, pkgs, ...}:
+  nodes.machine = { config, libs, pkgs, ...}:
   { services.kerberos_server =
     { enable = true;
       realms = {
diff --git a/nixos/tests/kerberos/mit.nix b/nixos/tests/kerberos/mit.nix
index 93b4020d49941..b475b7e4c92bb 100644
--- a/nixos/tests/kerberos/mit.nix
+++ b/nixos/tests/kerberos/mit.nix
@@ -1,6 +1,6 @@
 import ../make-test-python.nix ({pkgs, ...}: {
   name = "kerberos_server-mit";
-  machine = { config, libs, pkgs, ...}:
+  nodes.machine = { config, libs, pkgs, ...}:
   { services.kerberos_server =
     { enable = true;
       realms = {
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index 45c5c1963a0db..f34d5d6079404 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -12,7 +12,7 @@ let
       maintainers = [ nequissimus atemu ];
     };
 
-    machine = { ... }:
+    nodes.machine = { ... }:
       {
         boot.kernelPackages = linuxPackages;
       };
diff --git a/nixos/tests/kernel-latest-ath-user-regd.nix b/nixos/tests/kernel-latest-ath-user-regd.nix
index 11a3959e692e9..09e1da9d2affe 100644
--- a/nixos/tests/kernel-latest-ath-user-regd.nix
+++ b/nixos/tests/kernel-latest-ath-user-regd.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ veehaitch ];
   };
 
-  machine = { pkgs, ... }:
+  nodes.machine = { pkgs, ... }:
     {
       boot.kernelPackages = pkgs.linuxPackages_latest;
       networking.wireless.athUserRegulatoryDomain = true;
diff --git a/nixos/tests/kexec.nix b/nixos/tests/kexec.nix
index 010f3da49846a..55b71e0999f68 100644
--- a/nixos/tests/kexec.nix
+++ b/nixos/tests/kexec.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     maintainers = [ eelco ];
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
     { virtualisation.vlans = [ ]; };
 
   testScript =
diff --git a/nixos/tests/krb5/deprecated-config.nix b/nixos/tests/krb5/deprecated-config.nix
index 9a9cafd4b13e9..aca29ae6ca2b2 100644
--- a/nixos/tests/krb5/deprecated-config.nix
+++ b/nixos/tests/krb5/deprecated-config.nix
@@ -7,7 +7,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ eqyiel ];
   };
 
-  machine =
+  nodes.machine =
     { ... }: {
       krb5 = {
         enable = true;
diff --git a/nixos/tests/krb5/example-config.nix b/nixos/tests/krb5/example-config.nix
index 0932c71dd9705..1125b02f01ca8 100644
--- a/nixos/tests/krb5/example-config.nix
+++ b/nixos/tests/krb5/example-config.nix
@@ -7,7 +7,7 @@ import ../make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ eqyiel ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }: {
       krb5 = {
         enable = true;
diff --git a/nixos/tests/ksm.nix b/nixos/tests/ksm.nix
index 8f84b32020ab9..026d2ee85a24a 100644
--- a/nixos/tests/ksm.nix
+++ b/nixos/tests/ksm.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ lib, ...} :
     maintainers = [ rnhmjoj ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
 
     hardware.ksm.enable = true;
diff --git a/nixos/tests/libinput.nix b/nixos/tests/libinput.nix
index 2f84aaadcd0be..9b6fa159b999c 100644
--- a/nixos/tests/libinput.nix
+++ b/nixos/tests/libinput.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({ ... }:
 {
   name = "libinput";
 
-  machine = { ... }:
+  nodes.machine = { ... }:
     {
       imports = [
         ./common/x11.nix
diff --git a/nixos/tests/libresprite.nix b/nixos/tests/libresprite.nix
index 1a6210e3671ae..16d272acfa0fa 100644
--- a/nixos/tests/libresprite.nix
+++ b/nixos/tests/libresprite.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ fgaz ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
diff --git a/nixos/tests/lightdm.nix b/nixos/tests/lightdm.nix
index e98230ecb1794..94cebd4a630ab 100644
--- a/nixos/tests/lightdm.nix
+++ b/nixos/tests/lightdm.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ aszlig ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     imports = [ ./common/user-account.nix ];
     services.xserver.enable = true;
     services.xserver.displayManager.lightdm.enable = true;
diff --git a/nixos/tests/limesurvey.nix b/nixos/tests/limesurvey.nix
index b60e80be2444c..9a3193991f352 100644
--- a/nixos/tests/limesurvey.nix
+++ b/nixos/tests/limesurvey.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "limesurvey";
   meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.limesurvey = {
       enable = true;
       virtualHost = {
diff --git a/nixos/tests/litestream.nix b/nixos/tests/litestream.nix
index 886fbfef9cf55..f9d71c526e9e7 100644
--- a/nixos/tests/litestream.nix
+++ b/nixos/tests/litestream.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ jwygoda ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
     { services.litestream = {
         enable = true;
diff --git a/nixos/tests/login.nix b/nixos/tests/login.nix
index 4d1dcc8cc32da..0d6f81b172191 100644
--- a/nixos/tests/login.nix
+++ b/nixos/tests/login.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
     maintainers = [ eelco ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, lib, ... }:
     { boot.kernelPackages = lib.mkIf latestKernel pkgs.linuxPackages_latest;
       sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
diff --git a/nixos/tests/logrotate.nix b/nixos/tests/logrotate.nix
index 38da8d535275c..b0685f3af9ff1 100644
--- a/nixos/tests/logrotate.nix
+++ b/nixos/tests/logrotate.nix
@@ -1,26 +1,105 @@
 # Test logrotate service works and is enabled by default
 
-import ./make-test-python.nix ({ pkgs, ...} : rec {
+let
+  importTest = { ... }: {
+    services.logrotate.settings.import = {
+      olddir = false;
+    };
+  };
+
+in
+
+import ./make-test-python.nix ({ pkgs, ... }: rec {
   name = "logrotate";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ martinetd ];
   };
 
-  # default machine
-  machine = { ... }: {
+  nodes = {
+    defaultMachine = { ... }: { };
+    failingMachine = { ... }: {
+      services.logrotate.configFile = pkgs.writeText "logrotate.conf" ''
+        # self-written config file
+        su notarealuser notagroupeither
+      '';
+    };
+    machine = { config, ... }: {
+      imports = [ importTest ];
+
+      services.logrotate.settings = {
+        # remove default frequency header and add another
+        header = {
+          frequency = null;
+          delaycompress = true;
+        };
+        # extra global setting... affecting nothing
+        last_line = {
+          global = true;
+          priority = 2000;
+          shred = true;
+        };
+        # using mail somewhere should add --mail to logrotate invokation
+        sendmail = {
+          mail = "user@domain.tld";
+        };
+        # postrotate should be suffixed by 'endscript'
+        postrotate = {
+          postrotate = "touch /dev/null";
+        };
+        # check checkConfig works as expected: there is nothing to check here
+        # except that the file build passes
+        checkConf = {
+          su = "root utmp";
+          createolddir = "0750 root utmp";
+          create = "root utmp";
+          "create " = "0750 root utmp";
+        };
+        # multiple paths should be aggregated
+        multipath = {
+          files = [ "file1" "file2" ];
+        };
+        # overriding imported path should keep existing attributes
+        # (e.g. olddir is still set)
+        import = {
+          notifempty = true;
+        };
+      };
+      # extraConfig compatibility - should be added to top level, early.
+      services.logrotate.extraConfig = ''
+        nomail
+      '';
+      # paths compatibility
+      services.logrotate.paths = {
+        compat_path = {
+          path = "compat_test_path";
+        };
+        # user/group should be grouped as 'su user group'
+        compat_user = {
+          user = config.users.users.root.name;
+          group = "root";
+        };
+        # extraConfig in path should be added to block
+        compat_extraConfig = {
+          extraConfig = "dateext";
+        };
+        # keep -> rotate
+        compat_keep = {
+          keep = 1;
+        };
+      };
+    };
   };
 
   testScript =
     ''
       with subtest("whether logrotate works"):
-          machine.succeed(
-              # we must rotate once first to create logrotate stamp
-              "systemctl start logrotate.service")
+          # we must rotate once first to create logrotate stamp
+          defaultMachine.succeed("systemctl start logrotate.service")
           # we need to wait for console text once here to
           # clear console buffer up to this point for next wait
-          machine.wait_for_console_text('logrotate.service: Deactivated successfully')
+          defaultMachine.wait_for_console_text('logrotate.service: Deactivated successfully')
 
-          machine.succeed(
+          defaultMachine.succeed(
               # wtmp is present in default config.
               "rm -f /var/log/wtmp*",
               # we need to give it at least 1MB
@@ -28,10 +107,46 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
 
               # move into the future and check rotation.
               "date -s 'now + 1 month + 1 day'")
-          machine.wait_for_console_text('logrotate.service: Deactivated successfully')
-          machine.succeed(
+          defaultMachine.wait_for_console_text('logrotate.service: Deactivated successfully')
+          defaultMachine.succeed(
               # check rotate worked
               "[ -e /var/log/wtmp.1 ]",
           )
+      with subtest("default config does not have mail"):
+          defaultMachine.fail("systemctl cat logrotate.service | grep -- --mail")
+      with subtest("using mails adds mail option"):
+          machine.succeed("systemctl cat logrotate.service | grep -- --mail")
+      with subtest("check generated config matches expectation"):
+          machine.succeed(
+              # copy conf to /tmp/logrotate.conf for easy grep
+              "conf=$(systemctl cat logrotate | grep -oE '/nix/store[^ ]*logrotate.conf'); cp $conf /tmp/logrotate.conf",
+              "! grep weekly /tmp/logrotate.conf",
+              "grep -E '^delaycompress' /tmp/logrotate.conf",
+              "tail -n 1 /tmp/logrotate.conf | grep shred",
+              "sed -ne '/\"sendmail\" {/,/}/p' /tmp/logrotate.conf | grep 'mail user@domain.tld'",
+              "sed -ne '/\"postrotate\" {/,/}/p' /tmp/logrotate.conf | grep endscript",
+              "grep '\"file1\"\n\"file2\" {' /tmp/logrotate.conf",
+              "sed -ne '/\"import\" {/,/}/p' /tmp/logrotate.conf | grep noolddir",
+              "sed -ne '1,/^\"/p' /tmp/logrotate.conf | grep nomail",
+              "grep '\"compat_test_path\" {' /tmp/logrotate.conf",
+              "sed -ne '/\"compat_user\" {/,/}/p' /tmp/logrotate.conf | grep 'su root root'",
+              "sed -ne '/\"compat_extraConfig\" {/,/}/p' /tmp/logrotate.conf | grep dateext",
+              "[[ $(sed -ne '/\"compat_keep\" {/,/}/p' /tmp/logrotate.conf | grep -w rotate) = \"  rotate 1\" ]]",
+              "! sed -ne '/\"compat_keep\" {/,/}/p' /tmp/logrotate.conf | grep -w keep",
+          )
+          # also check configFile option
+          failingMachine.succeed(
+              "conf=$(systemctl cat logrotate | grep -oE '/nix/store[^ ]*logrotate.conf'); cp $conf /tmp/logrotate.conf",
+              "grep 'self-written config' /tmp/logrotate.conf",
+          )
+      with subtest("Check logrotate-checkconf service"):
+          machine.wait_for_unit("logrotate-checkconf.service")
+          # wait_for_unit also asserts for success, so wait for
+          # parent target instead and check manually.
+          failingMachine.wait_for_unit("multi-user.target")
+          info = failingMachine.get_unit_info("logrotate-checkconf.service")
+          if info["ActiveState"] != "failed":
+              raise Exception('logrotate-checkconf.service was not failed')
+
     '';
 })
diff --git a/nixos/tests/loki.nix b/nixos/tests/loki.nix
index 0c6dff3fdf137..470f80e9db635 100644
--- a/nixos/tests/loki.nix
+++ b/nixos/tests/loki.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
     maintainers = [ willibutz ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.loki = {
       enable = true;
       configFile = "${pkgs.grafana-loki.src}/cmd/loki/loki-local-config.yaml";
diff --git a/nixos/tests/lorri/default.nix b/nixos/tests/lorri/default.nix
index c33c7503993da..209b87f9f26a3 100644
--- a/nixos/tests/lorri/default.nix
+++ b/nixos/tests/lorri/default.nix
@@ -1,5 +1,5 @@
 import ../make-test-python.nix {
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ ../../modules/profiles/minimal.nix ];
     environment.systemPackages = [ pkgs.lorri ];
   };
diff --git a/nixos/tests/lxd-image-server.nix b/nixos/tests/lxd-image-server.nix
index 9f060fed38d87..fa40e33e74dd6 100644
--- a/nixos/tests/lxd-image-server.nix
+++ b/nixos/tests/lxd-image-server.nix
@@ -51,7 +51,7 @@ in {
     maintainers = [ mkg20001 ];
   };
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     virtualisation = {
       cores = 2;
 
diff --git a/nixos/tests/lxd-image.nix b/nixos/tests/lxd-image.nix
index 096b9d9aba906..4930b55f19094 100644
--- a/nixos/tests/lxd-image.nix
+++ b/nixos/tests/lxd-image.nix
@@ -44,7 +44,7 @@ in {
     maintainers = [ mkg20001 ];
   };
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     virtualisation = {
       # disk full otherwise
       diskSize = 2048;
diff --git a/nixos/tests/lxd-nftables.nix b/nixos/tests/lxd-nftables.nix
index a62d5a3064dfb..2930650015679 100644
--- a/nixos/tests/lxd-nftables.nix
+++ b/nixos/tests/lxd-nftables.nix
@@ -12,7 +12,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ patryk27 ];
   };
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     virtualisation = {
       lxd.enable = true;
     };
diff --git a/nixos/tests/lxd.nix b/nixos/tests/lxd.nix
index 1a3b84a85cf68..81b36124cc6b2 100644
--- a/nixos/tests/lxd.nix
+++ b/nixos/tests/lxd.nix
@@ -51,7 +51,7 @@ in {
     maintainers = [ patryk27 ];
   };
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     virtualisation = {
       # Since we're testing `limits.cpu`, we've gotta have a known number of
       # cores to lean on
diff --git a/nixos/tests/maestral.nix b/nixos/tests/maestral.nix
new file mode 100644
index 0000000000000..ba2e0b2f3baab
--- /dev/null
+++ b/nixos/tests/maestral.nix
@@ -0,0 +1,72 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "maestral";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ peterhoeg ];
+  };
+
+  nodes =
+    let
+      common = attrs:
+        pkgs.lib.recursiveUpdate
+          {
+            imports = [ ./common/user-account.nix ];
+            systemd.user.services.maestral = {
+              description = "Maestral Dropbox Client";
+              serviceConfig.Type = "exec";
+            };
+          }
+          attrs;
+
+    in
+    {
+      cli = { ... }: common {
+        systemd.user.services.maestral = {
+          wantedBy = [ "default.target" ];
+          serviceConfig.ExecStart = "${pkgs.maestral}/bin/maestral start --foreground";
+        };
+      };
+
+      gui = { ... }: common {
+        services.xserver = {
+          enable = true;
+          displayManager.sddm.enable = true;
+          displayManager.defaultSession = "plasma";
+          desktopManager.plasma5.enable = true;
+          desktopManager.plasma5.runUsingSystemd = true;
+          displayManager.autoLogin = {
+            enable = true;
+            user = "alice";
+          };
+        };
+
+        systemd.user.services = {
+          maestral = {
+            wantedBy = [ "graphical-session.target" ];
+            serviceConfig.ExecStart = "${pkgs.maestral-gui}/bin/maestral_qt";
+          };
+          # PowerDevil doesn't like our VM
+          plasma-powerdevil.enable = false;
+        };
+      };
+    };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.cli.config.users.users.alice;
+    in
+    ''
+      start_all()
+
+      with subtest("CLI"):
+        # we need SOME way to give the user an active login session
+        cli.execute("loginctl enable-linger ${user.name}")
+        cli.systemctl("start user@${toString user.uid}")
+        cli.wait_for_unit("maestral.service", "${user.name}")
+
+      with subtest("GUI"):
+        gui.wait_for_x()
+        gui.succeed("xauth merge ${user.home}/.Xauthority")
+        gui.wait_for_window("^Desktop ")
+        gui.wait_for_unit("maestral.service", "${user.name}")
+    '';
+})
diff --git a/nixos/tests/magnetico.nix b/nixos/tests/magnetico.nix
index 8433a974f453b..ee84aacaf7a74 100644
--- a/nixos/tests/magnetico.nix
+++ b/nixos/tests/magnetico.nix
@@ -9,7 +9,7 @@ in
     maintainers = [ rnhmjoj ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
 
     networking.firewall.allowedTCPPorts = [ 9000 ];
diff --git a/nixos/tests/mailcatcher.nix b/nixos/tests/mailcatcher.nix
index a55fba8a9950b..d7858ab354bd3 100644
--- a/nixos/tests/mailcatcher.nix
+++ b/nixos/tests/mailcatcher.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ lib, ... }:
   name = "mailcatcher";
   meta.maintainers = [ lib.maintainers.aanderse ];
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
     {
       services.mailcatcher.enable = true;
diff --git a/nixos/tests/mailhog.nix b/nixos/tests/mailhog.nix
index aece57178dd17..3508c2c0a5ea3 100644
--- a/nixos/tests/mailhog.nix
+++ b/nixos/tests/mailhog.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ lib, ... }: {
   name = "mailhog";
   meta.maintainers = with lib.maintainers; [ jojosch ];
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.mailhog.enable = true;
 
     environment.systemPackages = with pkgs; [ swaks ];
diff --git a/nixos/tests/matomo.nix b/nixos/tests/matomo.nix
index f6b0845749cde..526a24fc4db75 100644
--- a/nixos/tests/matomo.nix
+++ b/nixos/tests/matomo.nix
@@ -7,7 +7,7 @@ with pkgs.lib;
 let
   matomoTest = package:
   makeTest {
-    machine = { config, pkgs, ... }: {
+    nodes.machine = { config, pkgs, ... }: {
       services.matomo = {
         package = package;
         enable = true;
diff --git a/nixos/tests/matrix/pantalaimon.nix b/nixos/tests/matrix/pantalaimon.nix
index 1a9894dd21596..b5d649e6517aa 100644
--- a/nixos/tests/matrix/pantalaimon.nix
+++ b/nixos/tests/matrix/pantalaimon.nix
@@ -36,7 +36,7 @@ import ../make-test-python.nix (
       maintainers = teams.matrix.members;
     };
 
-    machine = { pkgs, ... }: {
+    nodes.machine = { pkgs, ... }: {
       services.pantalaimon-headless.instances.${pantalaimonInstanceName} = {
         homeserver = "https://localhost:8448";
         listenAddress = "0.0.0.0";
diff --git a/nixos/tests/mediawiki.nix b/nixos/tests/mediawiki.nix
index 702fefefa1610..7f31d6aadfa2c 100644
--- a/nixos/tests/mediawiki.nix
+++ b/nixos/tests/mediawiki.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "mediawiki";
   meta.maintainers = [ lib.maintainers.aanderse ];
 
-  machine =
+  nodes.machine =
     { ... }:
     { services.mediawiki.enable = true;
       services.mediawiki.virtualHost.hostName = "localhost";
diff --git a/nixos/tests/meilisearch.nix b/nixos/tests/meilisearch.nix
index c379bda74c59a..9f54aa97d6adc 100644
--- a/nixos/tests/meilisearch.nix
+++ b/nixos/tests/meilisearch.nix
@@ -12,7 +12,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
     name = "meilisearch";
     meta.maintainers = with lib.maintainers; [ Br1ght0ne ];
 
-    machine = { ... }: {
+    nodes.machine = { ... }: {
       environment.systemPackages = with pkgs; [ curl jq ];
       services.meilisearch = {
         enable = true;
diff --git a/nixos/tests/memcached.nix b/nixos/tests/memcached.nix
index 31f5627d25ceb..6549995110d79 100644
--- a/nixos/tests/memcached.nix
+++ b/nixos/tests/memcached.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "memcached";
 
-  machine = {
+  nodes.machine = {
     imports = [ ../modules/profiles/minimal.nix ];
     services.memcached.enable = true;
   };
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index 02513c4726c1b..0d5f0fe2f044c 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -8,7 +8,7 @@ in {
     maintainers = [ eelco ];
   };
 
-  machine =
+  nodes.machine =
     { lib, ... }:
     with lib;
     { swapDevices = mkOverride 0
diff --git a/nixos/tests/mod_perl.nix b/nixos/tests/mod_perl.nix
index 29a1eb6503fdf..f29d79ea6206a 100644
--- a/nixos/tests/mod_perl.nix
+++ b/nixos/tests/mod_perl.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = [ sgo ];
   };
 
-  machine = { config, lib, pkgs, ... }: {
+  nodes.machine = { config, lib, pkgs, ... }: {
     services.httpd = {
       enable = true;
       adminAddr = "admin@localhost";
diff --git a/nixos/tests/moodle.nix b/nixos/tests/moodle.nix
index 56aa62596c078..4570e89638822 100644
--- a/nixos/tests/moodle.nix
+++ b/nixos/tests/moodle.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "moodle";
   meta.maintainers = [ lib.maintainers.aanderse ];
 
-  machine =
+  nodes.machine =
     { ... }:
     { services.moodle.enable = true;
       services.moodle.virtualHost.hostName = "localhost";
diff --git a/nixos/tests/mtp.nix b/nixos/tests/mtp.nix
new file mode 100644
index 0000000000000..8f0835d75d3fe
--- /dev/null
+++ b/nixos/tests/mtp.nix
@@ -0,0 +1,109 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "mtp";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ matthewcroughan nixinator ];
+  };
+
+  nodes =
+  {
+    client = { config, pkgs, ... }: {
+      # DBUS runs only once a user session is created, which means a user has to
+      # login. Here, we log in as root. Once logged in, the gvfs-daemon service runs
+      # as UID 0 in User-0.service
+      services.getty.autologinUser = "root";
+
+      # XDG_RUNTIME_DIR is needed for running systemd-user services such as
+      # gvfs-daemon as root.
+      environment.variables.XDG_RUNTIME_DIR = "/run/user/0";
+
+      environment.systemPackages = with pkgs; [ usbutils glib jmtpfs tree ];
+      services.gvfs.enable = true;
+
+      # Creates a usb-mtp device inside the VM, which is mapped to the host's
+      # /tmp folder, it is able to write files to this location, but only has
+      # permissions to read its own creations.
+      virtualisation.qemu.options = [
+        "-usb"
+        "-device usb-mtp,rootdir=/tmp,readonly=false"
+      ];
+    };
+  };
+
+
+  testScript = { nodes, ... }:
+    let
+      # Creates a list of QEMU MTP devices matching USB ID (46f4:0004). This
+      # value can be sourced in a shell script. This is so we can loop over the
+      # devices we find, as this test may want to use more than one MTP device
+      # in future.
+      mtpDevices = pkgs.writeScript "mtpDevices.sh" ''
+        export mtpDevices=$(lsusb -d 46f4:0004 | awk {'print $2","$4'} | sed 's/[:-]/ /g')
+      '';
+      # Qemu is only capable of creating an MTP device with Picture Transfer
+      # Protocol. This means that gvfs must use gphoto2:// rather than mtp://
+      # when mounting.
+      # https://github.com/qemu/qemu/blob/970bc16f60937bcfd334f14c614bd4407c247961/hw/usb/dev-mtp.c#L278
+      gvfs = rec {
+        mountAllMtpDevices = pkgs.writeScript "mountAllMtpDevices.sh" ''
+          set -e
+          source ${mtpDevices}
+          for i in $mtpDevices
+          do
+            gio mount "gphoto2://[usb:$i]/"
+          done
+        '';
+        unmountAllMtpDevices = pkgs.writeScript "unmountAllMtpDevices.sh" ''
+          set -e
+          source ${mtpDevices}
+          for i in $mtpDevices
+          do
+            gio mount -u "gphoto2://[usb:$i]/"
+          done
+        '';
+        # gvfsTest:
+        # 1. Creates a 10M test file
+        # 2. Copies it to the device using GIO tools
+        # 3. Checks for corruption with `diff`
+        # 4. Removes the file, then unmounts the disks.
+        gvfsTest = pkgs.writeScript "gvfsTest.sh" ''
+          set -e
+          source ${mtpDevices}
+          ${mountAllMtpDevices}
+          dd if=/dev/urandom of=testFile10M bs=1M count=10
+          for i in $mtpDevices
+          do
+            gio copy ./testFile10M gphoto2://[usb:$i]/
+            ls -lah /run/user/0/gvfs/*/testFile10M
+            gio remove gphoto2://[usb:$i]/testFile10M
+          done
+          ${unmountAllMtpDevices}
+        '';
+      };
+      jmtpfs = {
+        # jmtpfsTest:
+        # 1. Mounts the device on a dir named `phone` using jmtpfs
+        # 2. Puts the current Nixpkgs libmtp version into a file
+        # 3. Checks for corruption with `diff`
+        # 4. Prints the directory tree
+        jmtpfsTest = pkgs.writeScript "jmtpfsTest.sh" ''
+          set -e
+          mkdir phone
+          jmtpfs phone
+          echo "${pkgs.libmtp.version}" > phone/tmp/testFile
+          echo "${pkgs.libmtp.version}" > testFile
+          diff phone/tmp/testFile testFile
+          tree phone
+        '';
+      };
+    in
+    # Using >&2 allows the results of the scripts to be printed to the terminal
+    # when building this test with Nix. Scripts would otherwise complete
+    # silently.
+    ''
+    start_all()
+    client.wait_for_unit("multi-user.target")
+    client.wait_for_unit("dbus.service")
+    client.succeed("${gvfs.gvfsTest} >&2")
+    client.succeed("${jmtpfs.jmtpfsTest} >&2")
+  '';
+})
diff --git a/nixos/tests/musescore.nix b/nixos/tests/musescore.nix
index 7fd80d70df124..18de0a5502390 100644
--- a/nixos/tests/musescore.nix
+++ b/nixos/tests/musescore.nix
@@ -17,7 +17,7 @@ in
     maintainers = [ turion ];
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
 
   {
     imports = [
diff --git a/nixos/tests/mysql/mysql-autobackup.nix b/nixos/tests/mysql/mysql-autobackup.nix
index 101122f7bdef8..b49466db0a9ce 100644
--- a/nixos/tests/mysql/mysql-autobackup.nix
+++ b/nixos/tests/mysql/mysql-autobackup.nix
@@ -17,7 +17,7 @@ let
     name = "${name}-automysqlbackup";
     meta.maintainers = [ lib.maintainers.aanderse ];
 
-    machine = {
+    nodes.machine = {
       services.mysql = {
         inherit package;
         enable = true;
diff --git a/nixos/tests/nagios.nix b/nixos/tests/nagios.nix
index e4d8dabedf72c..b6e45fc103afd 100644
--- a/nixos/tests/nagios.nix
+++ b/nixos/tests/nagios.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix (
       maintainers = [ symphorien ];
     };
 
-    machine = { lib, ... }: let
+    nodes.machine = { lib, ... }: let
       writer = pkgs.writeShellScript "write" ''
         set -x
         echo "$@"  >> /tmp/notifications
diff --git a/nixos/tests/navidrome.nix b/nixos/tests/navidrome.nix
index 42e14720b2edf..62290d50fc7e9 100644
--- a/nixos/tests/navidrome.nix
+++ b/nixos/tests/navidrome.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "navidrome";
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.navidrome.enable = true;
   };
 
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index dc7938a436aa7..bd517093eb3d0 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -640,7 +640,7 @@ let
     };
     virtual = {
       name = "Virtual";
-      machine = {
+      nodes.machine = {
         networking.useNetworkd = networkd;
         networking.useDHCP = false;
         networking.interfaces.tap0 = {
@@ -784,7 +784,7 @@ let
     };
     routes = {
       name = "routes";
-      machine = {
+      nodes.machine = {
         networking.useNetworkd = networkd;
         networking.useDHCP = false;
         networking.interfaces.eth0 = {
@@ -865,7 +865,7 @@ let
     };
     rename = {
       name = "RenameInterface";
-      machine = { pkgs, ... }: {
+      nodes.machine = { pkgs, ... }: {
         virtualisation.vlans = [ 1 ];
         networking = {
           useNetworkd = networkd;
@@ -916,7 +916,7 @@ let
       testMac = "06:00:00:00:02:00";
     in {
       name = "WlanInterface";
-      machine = { pkgs, ... }: {
+      nodes.machine = { pkgs, ... }: {
         boot.kernelModules = [ "mac80211_hwsim" ];
         networking.wlanInterfaces = {
           wlan0 = { device = "wlan0"; };
diff --git a/nixos/tests/nginx-modsecurity.nix b/nixos/tests/nginx-modsecurity.nix
index 8c53c0196d4cc..5ceee3787297d 100644
--- a/nixos/tests/nginx-modsecurity.nix
+++ b/nixos/tests/nginx-modsecurity.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "nginx-modsecurity";
 
-  machine = { config, lib, pkgs, ... }: {
+  nodes.machine = { config, lib, pkgs, ... }: {
     services.nginx = {
       enable = true;
       additionalModules = [ pkgs.nginxModules.modsecurity-nginx ];
diff --git a/nixos/tests/nginx-pubhtml.nix b/nixos/tests/nginx-pubhtml.nix
index 6e1e605628e9a..bff24c99d41a4 100644
--- a/nixos/tests/nginx-pubhtml.nix
+++ b/nixos/tests/nginx-pubhtml.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix {
   name = "nginx-pubhtml";
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
     services.nginx.enable = true;
     services.nginx.virtualHosts.localhost = {
diff --git a/nixos/tests/nginx-sandbox.nix b/nixos/tests/nginx-sandbox.nix
index 2d512725f2650..92ba30a09cf9f 100644
--- a/nixos/tests/nginx-sandbox.nix
+++ b/nixos/tests/nginx-sandbox.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
   # This test checks the creation and reading of a file in sandbox mode. Used simple lua script.
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     nixpkgs.overlays = [
       (self: super: {
         nginx-lua = super.nginx.override {
diff --git a/nixos/tests/nginx-sso.nix b/nixos/tests/nginx-sso.nix
index aeb89859c73f1..221c5f4ed9058 100644
--- a/nixos/tests/nginx-sso.nix
+++ b/nixos/tests/nginx-sso.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = with pkgs.lib.maintainers; [ delroth ];
   };
 
-  machine = {
+  nodes.machine = {
     services.nginx.sso = {
       enable = true;
       configuration = {
diff --git a/nixos/tests/nginx-variants.nix b/nixos/tests/nginx-variants.nix
index 96a9a2c3b8c1d..0faa0127669dd 100644
--- a/nixos/tests/nginx-variants.nix
+++ b/nixos/tests/nginx-variants.nix
@@ -13,7 +13,7 @@ builtins.listToAttrs (
         value = makeTest {
           name = "nginx-variant-${nginxName}";
 
-          machine = { pkgs, ... }: {
+          nodes.machine = { pkgs, ... }: {
             services.nginx = {
               enable = true;
               virtualHosts.localhost.locations."/".return = "200 'foo'";
diff --git a/nixos/tests/nix-serve.nix b/nixos/tests/nix-serve.nix
index ab82f4be43e68..3aa913f81107a 100644
--- a/nixos/tests/nix-serve.nix
+++ b/nixos/tests/nix-serve.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }:
 {
   name = "nix-serve";
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.nix-serve.enable = true;
     environment.systemPackages = [
       pkgs.hello
diff --git a/nixos/tests/nixos-generate-config.nix b/nixos/tests/nixos-generate-config.nix
index 1dadf4992ed00..e1c2f29e06732 100644
--- a/nixos/tests/nixos-generate-config.nix
+++ b/nixos/tests/nixos-generate-config.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ lib, ... } : {
   name = "nixos-generate-config";
   meta.maintainers = with lib.maintainers; [ basvandijk ];
-  machine = {
+  nodes.machine = {
     system.nixos-generate-config.configuration = ''
       # OVERRIDDEN
       { config, pkgs, ... }: {
diff --git a/nixos/tests/noto-fonts.nix b/nixos/tests/noto-fonts.nix
index 049dc766bd3b7..e4c33fe26a9e6 100644
--- a/nixos/tests/noto-fonts.nix
+++ b/nixos/tests/noto-fonts.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ nickcao midchildan ];
   };
 
-  machine = {
+  nodes.machine = {
     imports = [ ./common/x11.nix ];
     environment.systemPackages = [ pkgs.gnome.gedit ];
     fonts = {
diff --git a/nixos/tests/novacomd.nix b/nixos/tests/novacomd.nix
index b470c117e1e15..d47d212fb2eca 100644
--- a/nixos/tests/novacomd.nix
+++ b/nixos/tests/novacomd.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ dtzWill ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.novacomd.enable = true;
   };
 
diff --git a/nixos/tests/oh-my-zsh.nix b/nixos/tests/oh-my-zsh.nix
index 57a073b086e88..1d5227e36236d 100644
--- a/nixos/tests/oh-my-zsh.nix
+++ b/nixos/tests/oh-my-zsh.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "oh-my-zsh";
 
-  machine = { pkgs, ... }:
+  nodes.machine = { pkgs, ... }:
 
     {
       programs.zsh = {
diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix
index f1a39ad7dde2f..3c388119d5d24 100644
--- a/nixos/tests/openldap.nix
+++ b/nixos/tests/openldap.nix
@@ -25,7 +25,7 @@ in {
     inherit testScript;
     name = "openldap";
 
-    machine = { pkgs, ... }: {
+    nodes.machine = { pkgs, ... }: {
       environment.etc."openldap/root_password".text = "notapassword";
       services.openldap = {
         enable = true;
@@ -65,7 +65,7 @@ in {
     inherit testScript;
     name = "openldap";
 
-    machine = { pkgs, ... }: {
+    nodes.machine = { pkgs, ... }: {
       services.openldap = {
         enable = true;
         logLevel = "stats acl";
@@ -83,7 +83,7 @@ in {
   manualConfigDir = import ./make-test-python.nix ({ pkgs, ... }: {
     name = "openldap";
 
-    machine = { pkgs, ... }: {
+    nodes.machine = { pkgs, ... }: {
       services.openldap = {
         enable = true;
         configDir = "/var/db/slapd.d";
diff --git a/nixos/tests/opentabletdriver.nix b/nixos/tests/opentabletdriver.nix
index fe345a7bec735..b7583f6dd2648 100644
--- a/nixos/tests/opentabletdriver.nix
+++ b/nixos/tests/opentabletdriver.nix
@@ -6,7 +6,7 @@ in {
     maintainers = with pkgs.lib.maintainers; [ thiagokokada ];
   };
 
-  machine = { pkgs, ... }:
+  nodes.machine = { pkgs, ... }:
     {
       imports = [
         ./common/user-account.nix
diff --git a/nixos/tests/os-prober.nix b/nixos/tests/os-prober.nix
index 90375450fe1b2..ac05bd80c601a 100644
--- a/nixos/tests/os-prober.nix
+++ b/nixos/tests/os-prober.nix
@@ -65,7 +65,7 @@ let
 in {
   name = "os-prober";
 
-  machine = { config, pkgs, ... }: (simpleConfig // {
+  nodes.machine = { config, pkgs, ... }: (simpleConfig // {
       imports = [ ../modules/profiles/installation-device.nix
                   ../modules/profiles/base.nix ];
       virtualisation.memorySize = 1300;
diff --git a/nixos/tests/osrm-backend.nix b/nixos/tests/osrm-backend.nix
index 4067d5b1a239a..b0e65a2ae1c10 100644
--- a/nixos/tests/osrm-backend.nix
+++ b/nixos/tests/osrm-backend.nix
@@ -5,7 +5,7 @@ in {
   name = "osrm-backend";
   meta.maintainers = [ lib.maintainers.erictapen ];
 
-  machine = { config, pkgs, ... }:{
+  nodes.machine = { config, pkgs, ... }:{
 
     services.osrm = {
       enable = true;
diff --git a/nixos/tests/overlayfs.nix b/nixos/tests/overlayfs.nix
index 1768f1fea1ed1..6dab6760c5b99 100644
--- a/nixos/tests/overlayfs.nix
+++ b/nixos/tests/overlayfs.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "overlayfs";
   meta.maintainers = with pkgs.lib.maintainers; [ bachp ];
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     virtualisation.emptyDiskImages = [ 512 ];
     networking.hostId = "deadbeef";
     environment.systemPackages = with pkgs; [ parted ];
diff --git a/nixos/tests/packagekit.nix b/nixos/tests/packagekit.nix
index 020a4e65e6d8f..5769c6c9a8d45 100644
--- a/nixos/tests/packagekit.nix
+++ b/nixos/tests/packagekit.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ peterhoeg ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     environment.systemPackages = with pkgs; [ dbus ];
     services.packagekit = {
       enable = true;
diff --git a/nixos/tests/pam/pam-oath-login.nix b/nixos/tests/pam/pam-oath-login.nix
index 597596b211b11..8fb7553de9073 100644
--- a/nixos/tests/pam/pam-oath-login.nix
+++ b/nixos/tests/pam/pam-oath-login.nix
@@ -21,7 +21,7 @@ in
 {
   name = "pam-oath-login";
 
-  machine =
+  nodes.machine =
     { ... }:
     {
       security.pam.oath = {
diff --git a/nixos/tests/pam/pam-u2f.nix b/nixos/tests/pam/pam-u2f.nix
index 0ac6ac17be823..d7c540982cfa0 100644
--- a/nixos/tests/pam/pam-u2f.nix
+++ b/nixos/tests/pam/pam-u2f.nix
@@ -3,7 +3,7 @@ import ../make-test-python.nix ({ ... }:
 {
   name = "pam-u2f";
 
-  machine =
+  nodes.machine =
     { ... }:
     {
       security.pam.u2f = {
diff --git a/nixos/tests/pantheon.nix b/nixos/tests/pantheon.nix
index 989d29a966dfb..52f85f5c07da8 100644
--- a/nixos/tests/pantheon.nix
+++ b/nixos/tests/pantheon.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
     maintainers = teams.pantheon.members;
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
 
   {
     imports = [ ./common/user-account.nix ];
diff --git a/nixos/tests/php/fpm.nix b/nixos/tests/php/fpm.nix
index 718a635a6c7c9..64b61a377e28a 100644
--- a/nixos/tests/php/fpm.nix
+++ b/nixos/tests/php/fpm.nix
@@ -2,7 +2,7 @@ import ../make-test-python.nix ({ pkgs, lib, php, ... }: {
   name = "php-${php.version}-fpm-nginx-test";
   meta.maintainers = lib.teams.php.members;
 
-  machine = { config, lib, pkgs, ... }: {
+  nodes.machine = { config, lib, pkgs, ... }: {
     environment.systemPackages = [ php ];
 
     services.nginx = {
diff --git a/nixos/tests/php/httpd.nix b/nixos/tests/php/httpd.nix
index 36d90e72d7d14..b6dfbeeaed527 100644
--- a/nixos/tests/php/httpd.nix
+++ b/nixos/tests/php/httpd.nix
@@ -2,7 +2,7 @@ import ../make-test-python.nix ({ pkgs, lib, php, ... }: {
   name = "php-${php.version}-httpd-test";
   meta.maintainers = lib.teams.php.members;
 
-  machine = { config, lib, pkgs, ... }: {
+  nodes.machine = { config, lib, pkgs, ... }: {
     services.httpd = {
       enable = true;
       adminAddr = "admin@phpfpm";
diff --git a/nixos/tests/php/pcre.nix b/nixos/tests/php/pcre.nix
index 917184b975ec6..57407477f4b8e 100644
--- a/nixos/tests/php/pcre.nix
+++ b/nixos/tests/php/pcre.nix
@@ -5,7 +5,7 @@ import ../make-test-python.nix ({ lib, php, ... }: {
   name = "php-${php.version}-httpd-pcre-jit-test";
   meta.maintainers = lib.teams.php.members;
 
-  machine = { lib, pkgs, ... }: {
+  nodes.machine = { lib, pkgs, ... }: {
     time.timeZone = "UTC";
     services.httpd = {
       enable = true;
diff --git a/nixos/tests/pict-rs.nix b/nixos/tests/pict-rs.nix
index 432fd6a50ccd8..90f01d6d5d022 100644
--- a/nixos/tests/pict-rs.nix
+++ b/nixos/tests/pict-rs.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
     name = "pict-rs";
     meta.maintainers = with lib.maintainers; [ happysalada ];
 
-    machine = { ... }: {
+    nodes.machine = { ... }: {
       environment.systemPackages = with pkgs; [ curl jq ];
       services.pict-rs.enable = true;
     };
diff --git a/nixos/tests/plasma5-systemd-start.nix b/nixos/tests/plasma5-systemd-start.nix
index 72de19af70cef..f584c1ec137aa 100644
--- a/nixos/tests/plasma5-systemd-start.nix
+++ b/nixos/tests/plasma5-systemd-start.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     maintainers = [ oxalica ];
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
 
   {
     imports = [ ./common/user-account.nix ];
diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
index 5c7ea602f79e0..3358a72570e87 100644
--- a/nixos/tests/plasma5.nix
+++ b/nixos/tests/plasma5.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
     maintainers = [ ttuegel ];
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
 
   {
     imports = [ ./common/user-account.nix ];
diff --git a/nixos/tests/plausible.nix b/nixos/tests/plausible.nix
index 58c1dd5cf4a80..ab91e08beb349 100644
--- a/nixos/tests/plausible.nix
+++ b/nixos/tests/plausible.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = [ ma27 ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     virtualisation.memorySize = 4096;
     services.plausible = {
       enable = true;
diff --git a/nixos/tests/plikd.nix b/nixos/tests/plikd.nix
index 8fec93c01f6bf..643fd5bfcd376 100644
--- a/nixos/tests/plikd.nix
+++ b/nixos/tests/plikd.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ lib, ... }: {
     maintainers = [ freezeboy ];
   };
 
-  machine = { pkgs, ... }: let
+  nodes.machine = { pkgs, ... }: let
   in {
     services.plikd.enable = true;
     environment.systemPackages = [ pkgs.plik ];
diff --git a/nixos/tests/plotinus.nix b/nixos/tests/plotinus.nix
index af38b41813b7b..b6ebab9b01989 100644
--- a/nixos/tests/plotinus.nix
+++ b/nixos/tests/plotinus.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = pkgs.plotinus.meta.maintainers;
   };
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
 
     { imports = [ ./common/x11.nix ];
diff --git a/nixos/tests/postfix-raise-smtpd-tls-security-level.nix b/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
index 5fad1fed75b20..2a6c85a3a9202 100644
--- a/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
+++ b/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix {
   name = "postfix";
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ common/user-account.nix ];
     services.postfix = {
       enable = true;
diff --git a/nixos/tests/postfix.nix b/nixos/tests/postfix.nix
index 6d22b4edba0a2..1dbe6a4c51934 100644
--- a/nixos/tests/postfix.nix
+++ b/nixos/tests/postfix.nix
@@ -5,7 +5,7 @@ in
 import ./make-test-python.nix {
   name = "postfix";
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ common/user-account.nix ];
     services.postfix = {
       enable = true;
diff --git a/nixos/tests/postgresql-wal-receiver.nix b/nixos/tests/postgresql-wal-receiver.nix
index 0e8b3bfd6c34f..ae2708546f5db 100644
--- a/nixos/tests/postgresql-wal-receiver.nix
+++ b/nixos/tests/postgresql-wal-receiver.nix
@@ -31,7 +31,7 @@ let
         name = "postgresql-wal-receiver-${postgresqlPackage}";
         meta.maintainers = with lib.maintainers; [ pacien ];
 
-        machine = { ... }: {
+        nodes.machine = { ... }: {
           services.postgresql = {
             package = pkg;
             enable = true;
diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix
index 2b487c20a6258..7864f5d6ff32c 100644
--- a/nixos/tests/postgresql.nix
+++ b/nixos/tests/postgresql.nix
@@ -27,7 +27,7 @@ let
       maintainers = [ zagy ];
     };
 
-    machine = {...}:
+    nodes.machine = {...}:
       {
         services.postgresql = {
           enable = true;
diff --git a/nixos/tests/power-profiles-daemon.nix b/nixos/tests/power-profiles-daemon.nix
index e073677bee9d7..278e94711830a 100644
--- a/nixos/tests/power-profiles-daemon.nix
+++ b/nixos/tests/power-profiles-daemon.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
   meta = with pkgs.lib.maintainers; {
     maintainers = [ mvnetbiz ];
   };
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.power-profiles-daemon.enable = true;
     environment.systemPackages = [ pkgs.glib ];
   };
diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix
index c0b472638a14d..08773120bc127 100644
--- a/nixos/tests/predictable-interface-names.nix
+++ b/nixos/tests/predictable-interface-names.nix
@@ -16,7 +16,7 @@ in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd }: {
     name = "${if predictable then "" else "un"}predictableInterfaceNames${if withNetworkd then "-with-networkd" else ""}";
     meta = {};
 
-    machine = { lib, ... }: {
+    nodes.machine = { lib, ... }: {
       networking.usePredictableInterfaceNames = lib.mkForce predictable;
       networking.useNetworkd = withNetworkd;
       networking.dhcpcd.enable = !withNetworkd;
diff --git a/nixos/tests/privacyidea.nix b/nixos/tests/privacyidea.nix
index c1141465ec24e..fb072514dd90f 100644
--- a/nixos/tests/privacyidea.nix
+++ b/nixos/tests/privacyidea.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ...} : rec {
     maintainers = [ fpletz ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     virtualisation.cores = 2;
 
     services.privacyidea = {
diff --git a/nixos/tests/privoxy.nix b/nixos/tests/privoxy.nix
index d16cc498691fd..47072ce4b0afb 100644
--- a/nixos/tests/privoxy.nix
+++ b/nixos/tests/privoxy.nix
@@ -33,7 +33,7 @@ in
     maintainers = [ rnhmjoj ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.nginx.enable = true;
     services.nginx.virtualHosts."example.com" = {
       addSSL = true;
diff --git a/nixos/tests/pt2-clone.nix b/nixos/tests/pt2-clone.nix
index 364920c398711..ea4329c4a9806 100644
--- a/nixos/tests/pt2-clone.nix
+++ b/nixos/tests/pt2-clone.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ fgaz ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
diff --git a/nixos/tests/pulseaudio.nix b/nixos/tests/pulseaudio.nix
index 4e2ce679acd71..cfdc61bc6c2b8 100644
--- a/nixos/tests/pulseaudio.nix
+++ b/nixos/tests/pulseaudio.nix
@@ -27,7 +27,7 @@ let
           maintainers = [ synthetica ] ++ pkgs.pulseaudio.meta.maintainers;
         };
 
-        machine = { ... }:
+        nodes.machine = { ... }:
 
           {
             imports = [ ./common/wayland-cage.nix ];
diff --git a/nixos/tests/qboot.nix b/nixos/tests/qboot.nix
index 12aef6decfaed..29d999be58e5b 100644
--- a/nixos/tests/qboot.nix
+++ b/nixos/tests/qboot.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "qboot";
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     virtualisation.bios = pkgs.qboot;
   };
 
diff --git a/nixos/tests/rabbitmq.nix b/nixos/tests/rabbitmq.nix
index 03f1fa46d29e3..831335d8c5186 100644
--- a/nixos/tests/rabbitmq.nix
+++ b/nixos/tests/rabbitmq.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ eelco offline ];
   };
 
-  machine = {
+  nodes.machine = {
     services.rabbitmq = {
       enable = true;
       managementPlugin.enable = true;
diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix
index 5101628a682c6..66650dce4a008 100644
--- a/nixos/tests/radicale.nix
+++ b/nixos/tests/radicale.nix
@@ -11,7 +11,7 @@ in {
   name = "radicale3";
   meta.maintainers = with lib.maintainers; [ dotlambda ];
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.radicale = {
       enable = true;
       settings = {
diff --git a/nixos/tests/rasdaemon.nix b/nixos/tests/rasdaemon.nix
index e4bd8d96a8d53..7f30a3b81ab55 100644
--- a/nixos/tests/rasdaemon.nix
+++ b/nixos/tests/rasdaemon.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... } : {
     maintainers = [ evils ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
     hardware.rasdaemon = {
       enable = true;
diff --git a/nixos/tests/redmine.nix b/nixos/tests/redmine.nix
index 3866a1f528c05..621b3e6a36eee 100644
--- a/nixos/tests/redmine.nix
+++ b/nixos/tests/redmine.nix
@@ -9,7 +9,7 @@ with pkgs.lib;
 let
   redmineTest = { name, type }: makeTest {
     name = "redmine-${name}";
-    machine = { config, pkgs, ... }: {
+    nodes.machine = { config, pkgs, ... }: {
       services.redmine = {
         enable = true;
         package = pkgs.redmine;
diff --git a/nixos/tests/restart-by-activation-script.nix b/nixos/tests/restart-by-activation-script.nix
index 0eec292ea9e2b..0ac079e0101e0 100644
--- a/nixos/tests/restart-by-activation-script.nix
+++ b/nixos/tests/restart-by-activation-script.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ das_j ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
 
     systemd.services.restart-me = {
diff --git a/nixos/tests/retroarch.nix b/nixos/tests/retroarch.nix
index 4c96f9eabc824..c506ed02da89b 100644
--- a/nixos/tests/retroarch.nix
+++ b/nixos/tests/retroarch.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     name = "retroarch";
     meta = with pkgs.lib.maintainers; { maintainers = [ j0hax ]; };
 
-    machine = { ... }:
+    nodes.machine = { ... }:
 
       {
         imports = [ ./common/user-account.nix ];
diff --git a/nixos/tests/riak.nix b/nixos/tests/riak.nix
index 3dd4e333d6691..e75d40fa25695 100644
--- a/nixos/tests/riak.nix
+++ b/nixos/tests/riak.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     maintainers = [ Br1ght0ne ];
   };
 
-  machine = {
+  nodes.machine = {
     services.riak.enable = true;
     services.riak.package = pkgs.riak;
   };
diff --git a/nixos/tests/rspamd.nix b/nixos/tests/rspamd.nix
index f0ccfe7ea0e6a..26895fbad3f3b 100644
--- a/nixos/tests/rspamd.nix
+++ b/nixos/tests/rspamd.nix
@@ -22,7 +22,7 @@ let
   '';
   simple = name: enableIPv6: makeTest {
     name = "rspamd-${name}";
-    machine = {
+    nodes.machine = {
       services.rspamd.enable = true;
       networking.enableIPv6 = enableIPv6;
     };
@@ -52,7 +52,7 @@ in
   ipv4only = simple "ipv4only" false;
   deprecated = makeTest {
     name = "rspamd-deprecated";
-    machine = {
+    nodes.machine = {
       services.rspamd = {
         enable = true;
         workers.normal.bindSockets = [{
@@ -91,7 +91,7 @@ in
 
   bindports = makeTest {
     name = "rspamd-bindports";
-    machine = {
+    nodes.machine = {
       services.rspamd = {
         enable = true;
         workers.normal.bindSockets = [{
@@ -152,7 +152,7 @@ in
   };
   customLuaRules = makeTest {
     name = "rspamd-custom-lua-rules";
-    machine = {
+    nodes.machine = {
       environment.etc."tests/no-muh.eml".text = ''
         From: Sheep1<bah@example.com>
         To: Sheep2<mah@example.com>
@@ -256,7 +256,7 @@ in
   };
   postfixIntegration = makeTest {
     name = "rspamd-postfix-integration";
-    machine = {
+    nodes.machine = {
       environment.systemPackages = with pkgs; [ msmtp ];
       environment.etc."tests/gtube.eml".text = ''
         From: Sheep1<bah@example.com>
diff --git a/nixos/tests/rsyslogd.nix b/nixos/tests/rsyslogd.nix
index f35db3bd44b83..049acdcd43934 100644
--- a/nixos/tests/rsyslogd.nix
+++ b/nixos/tests/rsyslogd.nix
@@ -11,7 +11,7 @@ with pkgs.lib;
     name = "rsyslogd-test1";
     meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
 
-    machine = { config, pkgs, ... }: {
+    nodes.machine = { config, pkgs, ... }: {
       services.rsyslogd.enable = true;
       services.journald.forwardToSyslog = false;
     };
@@ -27,7 +27,7 @@ with pkgs.lib;
     name = "rsyslogd-test2";
     meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
 
-    machine = { config, pkgs, ... }: {
+    nodes.machine = { config, pkgs, ... }: {
       services.rsyslogd.enable = true;
     };
 
diff --git a/nixos/tests/sabnzbd.nix b/nixos/tests/sabnzbd.nix
index fb35b212b493b..075bd0b1fe093 100644
--- a/nixos/tests/sabnzbd.nix
+++ b/nixos/tests/sabnzbd.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with maintainers; [ jojosch ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.sabnzbd = {
       enable = true;
     };
diff --git a/nixos/tests/sddm.nix b/nixos/tests/sddm.nix
index d7c65fa33d67c..c76a9683e66d0 100644
--- a/nixos/tests/sddm.nix
+++ b/nixos/tests/sddm.nix
@@ -12,7 +12,7 @@ let
     default = {
       name = "sddm";
 
-      machine = { ... }: {
+      nodes.machine = { ... }: {
         imports = [ ./common/user-account.nix ];
         services.xserver.enable = true;
         services.xserver.displayManager.sddm.enable = true;
@@ -41,7 +41,7 @@ let
         maintainers = [ ttuegel ];
       };
 
-      machine = { ... }: {
+      nodes.machine = { ... }: {
         imports = [ ./common/user-account.nix ];
         services.xserver.enable = true;
         services.xserver.displayManager = {
diff --git a/nixos/tests/shattered-pixel-dungeon.nix b/nixos/tests/shattered-pixel-dungeon.nix
index d4e5de22ab9d0..a256bbdfd7357 100644
--- a/nixos/tests/shattered-pixel-dungeon.nix
+++ b/nixos/tests/shattered-pixel-dungeon.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ fgaz ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
diff --git a/nixos/tests/shiori.nix b/nixos/tests/shiori.nix
index 6c59c394009e6..d0f68b903f8c3 100644
--- a/nixos/tests/shiori.nix
+++ b/nixos/tests/shiori.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...}:
   name = "shiori";
   meta.maintainers = with lib.maintainers; [ minijackson ];
 
-  machine =
+  nodes.machine =
     { ... }:
     { services.shiori.enable = true; };
 
diff --git a/nixos/tests/signal-desktop.nix b/nixos/tests/signal-desktop.nix
index 8c72306299230..fbe9cdf84d05a 100644
--- a/nixos/tests/signal-desktop.nix
+++ b/nixos/tests/signal-desktop.nix
@@ -16,7 +16,7 @@ in {
     maintainers = [ flokli primeos ];
   };
 
-  machine = { ... }:
+  nodes.machine = { ... }:
 
   {
     imports = [
diff --git a/nixos/tests/simple.nix b/nixos/tests/simple.nix
index b4d90f750ecfb..c36287b4e843b 100644
--- a/nixos/tests/simple.nix
+++ b/nixos/tests/simple.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ eelco ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
   };
 
diff --git a/nixos/tests/snapper.nix b/nixos/tests/snapper.nix
index 098d8d9d72f55..651adc8934d38 100644
--- a/nixos/tests/snapper.nix
+++ b/nixos/tests/snapper.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ ... }:
 {
   name = "snapper";
 
-  machine = { pkgs, lib, ... }: {
+  nodes.machine = { pkgs, lib, ... }: {
     boot.initrd.postDeviceCommands = ''
       ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux /dev/vdb
     '';
diff --git a/nixos/tests/soapui.nix b/nixos/tests/soapui.nix
index 76a87ed5efa1c..e4ce3888fd437 100644
--- a/nixos/tests/soapui.nix
+++ b/nixos/tests/soapui.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ asbachb ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
diff --git a/nixos/tests/solr.nix b/nixos/tests/solr.nix
index 86efe87c70783..33afe9d788f78 100644
--- a/nixos/tests/solr.nix
+++ b/nixos/tests/solr.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
   name = "solr";
   meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
 
-  machine =
+  nodes.machine =
     { config, pkgs, ... }:
     {
       # Ensure the virtual machine has enough memory for Solr to avoid the following error:
diff --git a/nixos/tests/sourcehut.nix b/nixos/tests/sourcehut.nix
index 55757e35f9b43..34a60247e00e5 100644
--- a/nixos/tests/sourcehut.nix
+++ b/nixos/tests/sourcehut.nix
@@ -119,7 +119,7 @@ in
 
   meta.maintainers = [ pkgs.lib.maintainers.tomberek ];
 
-  machine = { config, pkgs, nodes, ... }: {
+  nodes.machine = { config, pkgs, nodes, ... }: {
     # buildsrht needs space
     virtualisation.diskSize = 4 * 1024;
     virtualisation.memorySize = 2 * 1024;
diff --git a/nixos/tests/sssd-ldap.nix b/nixos/tests/sssd-ldap.nix
index 5c58eaef7146f..f816c0652cc55 100644
--- a/nixos/tests/sssd-ldap.nix
+++ b/nixos/tests/sssd-ldap.nix
@@ -13,7 +13,7 @@ in import ./make-test-python.nix ({pkgs, ...}: {
     maintainers = [ bbigras ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.openldap = {
       enable = true;
       settings = {
diff --git a/nixos/tests/sssd.nix b/nixos/tests/sssd.nix
index 5c1abdca6aef4..25527cb59a59b 100644
--- a/nixos/tests/sssd.nix
+++ b/nixos/tests/sssd.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
   meta = with pkgs.lib.maintainers; {
     maintainers = [ bbigras ];
   };
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.sssd.enable = true;
   };
 
diff --git a/nixos/tests/starship.nix b/nixos/tests/starship.nix
index 33e9a72f70000..48a4be6caf176 100644
--- a/nixos/tests/starship.nix
+++ b/nixos/tests/starship.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   name = "starship";
   meta.maintainers = pkgs.starship.meta.maintainers;
 
-  machine = {
+  nodes.machine = {
     programs = {
       fish.enable = true;
       zsh.enable = true;
diff --git a/nixos/tests/sway.nix b/nixos/tests/sway.nix
index 1e9e146c4b6ce..8f95f2a030d1b 100644
--- a/nixos/tests/sway.nix
+++ b/nixos/tests/sway.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ primeos synthetica ];
   };
 
-  machine = { config, ... }: {
+  nodes.machine = { config, ... }: {
     # Automatically login on tty1 as a normal user:
     imports = [ ./common/user-account.nix ];
     services.getty.autologinUser = "alice";
diff --git a/nixos/tests/sympa.nix b/nixos/tests/sympa.nix
index aad7c95b6c99c..76ca17d0a1890 100644
--- a/nixos/tests/sympa.nix
+++ b/nixos/tests/sympa.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "sympa";
   meta.maintainers = with lib.maintainers; [ mmilata ];
 
-  machine =
+  nodes.machine =
     { ... }:
     {
 
diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing-init.nix
index 8b60ad7faf090..fcd90739e6a55 100644
--- a/nixos/tests/syncthing-init.nix
+++ b/nixos/tests/syncthing-init.nix
@@ -6,7 +6,7 @@ in {
   name = "syncthing-init";
   meta.maintainers = with pkgs.lib.maintainers; [ lassulus ];
 
-  machine = {
+  nodes.machine = {
     services.syncthing = {
       enable = true;
       devices.testDevice = {
diff --git a/nixos/tests/syncthing-relay.nix b/nixos/tests/syncthing-relay.nix
index a0233c969ec06..3d70b1eda7b2a 100644
--- a/nixos/tests/syncthing-relay.nix
+++ b/nixos/tests/syncthing-relay.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "syncthing-relay";
   meta.maintainers = with pkgs.lib.maintainers; [ delroth ];
 
-  machine = {
+  nodes.machine = {
     environment.systemPackages = [ pkgs.jq ];
     services.syncthing.relay = {
       enable = true;
diff --git a/nixos/tests/systemd-analyze.nix b/nixos/tests/systemd-analyze.nix
index 186f5aee7b85e..31588e2b41aa5 100644
--- a/nixos/tests/systemd-analyze.nix
+++ b/nixos/tests/systemd-analyze.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
     maintainers = [ raskin ];
   };
 
-  machine =
+  nodes.machine =
     { pkgs, lib, ... }:
     { boot.kernelPackages = lib.mkIf latestKernel pkgs.linuxPackages_latest;
       sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
diff --git a/nixos/tests/systemd-binfmt.nix b/nixos/tests/systemd-binfmt.nix
index a3a6efac3e4dc..b16fda0ddb1a6 100644
--- a/nixos/tests/systemd-binfmt.nix
+++ b/nixos/tests/systemd-binfmt.nix
@@ -31,7 +31,7 @@ let
 in {
   basic = makeTest {
     name = "systemd-binfmt";
-    machine = {
+    nodes.machine = {
       boot.binfmt.emulatedSystems = [
         "armv7l-linux"
         "aarch64-linux"
@@ -56,7 +56,7 @@ in {
 
   preserveArgvZero = makeTest {
     name = "systemd-binfmt-preserve-argv0";
-    machine = {
+    nodes.machine = {
       boot.binfmt.emulatedSystems = [
         "aarch64-linux"
       ];
@@ -71,7 +71,7 @@ in {
 
   ldPreload = makeTest {
     name = "systemd-binfmt-ld-preload";
-    machine = {
+    nodes.machine = {
       boot.binfmt.emulatedSystems = [
         "aarch64-linux"
       ];
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index 51cfd82e6c4bb..039e6bdd9d5ab 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -20,7 +20,7 @@ in
     name = "systemd-boot";
     meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer ];
 
-    machine = common;
+    nodes.machine = common;
 
     testScript = ''
       machine.start()
@@ -44,7 +44,7 @@ in
     name = "systemd-boot-specialisation";
     meta.maintainers = with pkgs.lib.maintainers; [ lukegb ];
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       specialisation.something.configuration = {};
     };
@@ -67,7 +67,7 @@ in
     name = "systemd-boot-fallback";
     meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer ];
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       boot.loader.efi.canTouchEfiVariables = mkForce false;
     };
@@ -93,7 +93,7 @@ in
     name = "systemd-boot-update";
     meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer ];
 
-    machine = common;
+    nodes.machine = common;
 
     testScript = ''
       machine.succeed("mount -o remount,rw /boot")
@@ -115,7 +115,7 @@ in
     name = "systemd-boot-memtest86";
     meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       boot.loader.systemd-boot.memtest86.enable = true;
       nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
@@ -133,7 +133,7 @@ in
     name = "systemd-boot-netbootxyz";
     meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       boot.loader.systemd-boot.netbootxyz.enable = true;
     };
@@ -148,7 +148,7 @@ in
     name = "systemd-boot-entry-filename";
     meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       boot.loader.systemd-boot.memtest86.enable = true;
       boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf";
@@ -168,7 +168,7 @@ in
     name = "systemd-boot-extra-entries";
     meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       boot.loader.systemd-boot.extraEntries = {
         "banana.conf" = ''
@@ -187,7 +187,7 @@ in
     name = "systemd-boot-extra-files";
     meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
 
-    machine = { pkgs, lib, ... }: {
+    nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
       boot.loader.systemd-boot.extraFiles = {
         "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
diff --git a/nixos/tests/systemd-confinement.nix b/nixos/tests/systemd-confinement.nix
index 3181af309a6e2..bde5b770ea50d 100644
--- a/nixos/tests/systemd-confinement.nix
+++ b/nixos/tests/systemd-confinement.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix {
   name = "systemd-confinement";
 
-  machine = { pkgs, lib, ... }: let
+  nodes.machine = { pkgs, lib, ... }: let
     testServer = pkgs.writeScript "testserver.sh" ''
       #!${pkgs.runtimeShell}
       export PATH=${lib.escapeShellArg "${pkgs.coreutils}/bin"}
diff --git a/nixos/tests/systemd-cryptenroll.nix b/nixos/tests/systemd-cryptenroll.nix
index 49634ef65672c..055ae7d1681f2 100644
--- a/nixos/tests/systemd-cryptenroll.nix
+++ b/nixos/tests/systemd-cryptenroll.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ ymatsiuk ];
   };
 
-  machine = { pkgs, lib, ... }: {
+  nodes.machine = { pkgs, lib, ... }: {
     environment.systemPackages = [ pkgs.cryptsetup ];
     virtualisation = {
       emptyDiskImages = [ 512 ];
diff --git a/nixos/tests/systemd-escaping.nix b/nixos/tests/systemd-escaping.nix
index 7f93eb5e4f70c..29d2ed1aa3523 100644
--- a/nixos/tests/systemd-escaping.nix
+++ b/nixos/tests/systemd-escaping.nix
@@ -14,7 +14,7 @@ in
 {
   name = "systemd-escaping";
 
-  machine = { pkgs, lib, utils, ... }: {
+  nodes.machine = { pkgs, lib, utils, ... }: {
     systemd.services.echo =
       assert !(builtins.tryEval (utils.escapeSystemdExecArgs [ [] ])).success;
       assert !(builtins.tryEval (utils.escapeSystemdExecArgs [ {} ])).success;
diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix
new file mode 100644
index 0000000000000..ba62cdf3bbc77
--- /dev/null
+++ b/nixos/tests/systemd-initrd-simple.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-simple";
+
+  machine = { pkgs, ... }: {
+    boot.initrd.systemd = {
+      enable = true;
+      emergencyAccess = true;
+    };
+    fileSystems = lib.mkVMOverride {
+      "/".autoResize = true;
+    };
+  };
+
+  testScript = ''
+    import subprocess
+
+    oldAvail = machine.succeed("df --output=avail / | sed 1d")
+    machine.shutdown()
+
+    subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
+
+    machine.start()
+    newAvail = machine.succeed("df --output=avail / | sed 1d")
+
+    assert int(oldAvail) < int(newAvail), "File system did not grow"
+  '';
+})
diff --git a/nixos/tests/systemd-journal.nix b/nixos/tests/systemd-journal.nix
index 6ab7c72463181..d2063a3b9a44e 100644
--- a/nixos/tests/systemd-journal.nix
+++ b/nixos/tests/systemd-journal.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     maintainers = [ lewo ];
   };
 
-  machine = { pkgs, lib, ... }: {
+  nodes.machine = { pkgs, lib, ... }: {
     services.journald.enableHttpGateway = true;
   };
 
diff --git a/nixos/tests/systemd-machinectl.nix b/nixos/tests/systemd-machinectl.nix
index 4fc5864357c00..ce0c56a360e96 100644
--- a/nixos/tests/systemd-machinectl.nix
+++ b/nixos/tests/systemd-machinectl.nix
@@ -28,7 +28,7 @@ import ./make-test-python.nix (
   {
     name = "systemd-machinectl";
 
-    machine = { lib, ... }: {
+    nodes.machine = { lib, ... }: {
       # use networkd to obtain systemd network setup
       networking.useNetworkd = true;
       networking.useDHCP = false;
diff --git a/nixos/tests/systemd-misc.nix b/nixos/tests/systemd-misc.nix
index e416baa8b5f56..0ddd51100463e 100644
--- a/nixos/tests/systemd-misc.nix
+++ b/nixos/tests/systemd-misc.nix
@@ -31,7 +31,7 @@ in
 {
   name = "systemd-misc";
 
-  machine = { pkgs, lib, ... }: {
+  nodes.machine = { pkgs, lib, ... }: {
     boot.extraSystemdUnitPaths = [ "/etc/systemd-rw/system" ];
 
     users.users.limited = {
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index f86daa5eea974..3317823e03f76 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "systemd";
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     imports = [ common/user-account.nix common/x11.nix ];
 
     virtualisation.emptyDiskImages = [ 512 512 ];
@@ -192,5 +192,9 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     with subtest("systemd per-unit accounting works"):
         assert "IP traffic received: 84B" in output_ping
         assert "IP traffic sent: 84B" in output_ping
+
+    with subtest("systemd environment is properly set"):
+        machine.systemctl("daemon-reexec")  # Rewrites /proc/1/environ
+        machine.succeed("grep -q TZDIR=/etc/zoneinfo /proc/1/environ")
   '';
 })
diff --git a/nixos/tests/telegraf.nix b/nixos/tests/telegraf.nix
index d99680ce2c3c4..c3cdb1645213a 100644
--- a/nixos/tests/telegraf.nix
+++ b/nixos/tests/telegraf.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ mic92 ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     services.telegraf.enable = true;
     services.telegraf.environmentFiles = [(pkgs.writeText "secrets" ''
       SECRET=example
diff --git a/nixos/tests/tinywl.nix b/nixos/tests/tinywl.nix
index 8fb87b533306c..411cdb1f64192 100644
--- a/nixos/tests/tinywl.nix
+++ b/nixos/tests/tinywl.nix
@@ -6,7 +6,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       maintainers = with lib.maintainers; [ primeos ];
     };
 
-    machine = { config, ... }: {
+    nodes.machine = { config, ... }: {
       # Automatically login on tty1 as a normal user:
       imports = [ ./common/user-account.nix ];
       services.getty.autologinUser = "alice";
diff --git a/nixos/tests/tomcat.nix b/nixos/tests/tomcat.nix
index e383f224e3d16..4cfb3cc5a7d84 100644
--- a/nixos/tests/tomcat.nix
+++ b/nixos/tests/tomcat.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
 {
   name = "tomcat";
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     services.tomcat.enable = true;
   };
 
diff --git a/nixos/tests/transmission.nix b/nixos/tests/transmission.nix
index 7e2648804de21..b69ddd84d009a 100644
--- a/nixos/tests/transmission.nix
+++ b/nixos/tests/transmission.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ coconnor ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
 
     networking.firewall.allowedTCPPorts = [ 9091 ];
diff --git a/nixos/tests/tsm-client-gui.nix b/nixos/tests/tsm-client-gui.nix
index e4bcd344a8957..e11501da53d0c 100644
--- a/nixos/tests/tsm-client-gui.nix
+++ b/nixos/tests/tsm-client-gui.nix
@@ -10,7 +10,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
 
   enableOCR = true;
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ ./common/x11.nix ];
     programs.tsmClient = {
       enable = true;
diff --git a/nixos/tests/tuptime.nix b/nixos/tests/tuptime.nix
index 6d37e30698390..93410de7bdf52 100644
--- a/nixos/tests/tuptime.nix
+++ b/nixos/tests/tuptime.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ evils ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
     services.tuptime.enable = true;
   };
diff --git a/nixos/tests/turbovnc-headless-server.nix b/nixos/tests/turbovnc-headless-server.nix
index 7d705c56ecf31..1dbf9297c8131 100644
--- a/nixos/tests/turbovnc-headless-server.nix
+++ b/nixos/tests/turbovnc-headless-server.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     maintainers = with lib.maintainers; [ nh2 ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
 
     environment.systemPackages = with pkgs; [
       glxinfo
diff --git a/nixos/tests/tuxguitar.nix b/nixos/tests/tuxguitar.nix
index 63a7b6c7dec9b..037f489e54483 100644
--- a/nixos/tests/tuxguitar.nix
+++ b/nixos/tests/tuxguitar.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ asbachb ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
diff --git a/nixos/tests/udisks2.nix b/nixos/tests/udisks2.nix
index 6c4b71aaa2eda..6afb200f85664 100644
--- a/nixos/tests/udisks2.nix
+++ b/nixos/tests/udisks2.nix
@@ -15,7 +15,7 @@ in
     maintainers = [ eelco ];
   };
 
-  machine =
+  nodes.machine =
     { ... }:
     { services.udisks2.enable = true;
       imports = [ ./common/user-account.nix ];
diff --git a/nixos/tests/usbguard.nix b/nixos/tests/usbguard.nix
index bb707bdbf7024..d6d3a80c5d23c 100644
--- a/nixos/tests/usbguard.nix
+++ b/nixos/tests/usbguard.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ tnias ];
   };
 
-  machine =
+  nodes.machine =
     { ... }:
     {
       services.usbguard = {
diff --git a/nixos/tests/user-activation-scripts.nix b/nixos/tests/user-activation-scripts.nix
index 0de8664c5ef07..9345735781873 100644
--- a/nixos/tests/user-activation-scripts.nix
+++ b/nixos/tests/user-activation-scripts.nix
@@ -2,7 +2,7 @@ import ./make-test-python.nix ({ lib, ... }: {
   name = "user-activation-scripts";
   meta = with lib.maintainers; { maintainers = [ chkno ]; };
 
-  machine = {
+  nodes.machine = {
     system.userActivationScripts.foo = "mktemp ~/user-activation-ran.XXXXXX";
     users.users.alice = {
       initialPassword = "pass1";
diff --git a/nixos/tests/uwsgi.nix b/nixos/tests/uwsgi.nix
index 80dcde324aad7..62da9e0a7168c 100644
--- a/nixos/tests/uwsgi.nix
+++ b/nixos/tests/uwsgi.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     maintainers = [ lnl7 ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     users.users.hello  =
       { isSystemUser = true;
         group = "hello";
diff --git a/nixos/tests/v2ray.nix b/nixos/tests/v2ray.nix
index 4808e149d31ed..fb36ea8557d59 100644
--- a/nixos/tests/v2ray.nix
+++ b/nixos/tests/v2ray.nix
@@ -57,7 +57,7 @@ in {
   meta = with lib.maintainers; {
     maintainers = [ servalcatty ];
   };
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     environment.systemPackages = [ pkgs.curl ];
     services.v2ray = {
       enable = true;
diff --git a/nixos/tests/vault-postgresql.nix b/nixos/tests/vault-postgresql.nix
index 2847af13cbf05..e0e5881c6da79 100644
--- a/nixos/tests/vault-postgresql.nix
+++ b/nixos/tests/vault-postgresql.nix
@@ -11,7 +11,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
   meta = with pkgs.lib.maintainers; {
     maintainers = [ lnl7 roberth ];
   };
-  machine = { lib, pkgs, ... }: {
+  nodes.machine = { lib, pkgs, ... }: {
     environment.systemPackages = [ pkgs.vault ];
     environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
     services.vault.enable = true;
diff --git a/nixos/tests/vault.nix b/nixos/tests/vault.nix
index e86acd5b593fb..1b0a26a4487f0 100644
--- a/nixos/tests/vault.nix
+++ b/nixos/tests/vault.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
   meta = with pkgs.lib.maintainers; {
     maintainers = [ lnl7 ];
   };
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     environment.systemPackages = [ pkgs.vault ];
     environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
     services.vault.enable = true;
diff --git a/nixos/tests/vector.nix b/nixos/tests/vector.nix
index 583e60ddc5681..ecf94e33ff17e 100644
--- a/nixos/tests/vector.nix
+++ b/nixos/tests/vector.nix
@@ -9,7 +9,7 @@ with pkgs.lib;
     name = "vector-test1";
     meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
 
-    machine = { config, pkgs, ... }: {
+    nodes.machine = { config, pkgs, ... }: {
       services.vector = {
         enable = true;
         journaldAccess = true;
diff --git a/nixos/tests/vengi-tools.nix b/nixos/tests/vengi-tools.nix
index 6b90542887d51..8b80a13384e5a 100644
--- a/nixos/tests/vengi-tools.nix
+++ b/nixos/tests/vengi-tools.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ fgaz ];
   };
 
-  machine = { config, pkgs, ... }: {
+  nodes.machine = { config, pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index f15412d365fa9..27093aab96ee5 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -353,7 +353,7 @@ let
   mkVBoxTest = useExtensionPack: vms: name: testScript: makeTest {
     name = "virtualbox-${name}";
 
-    machine = { lib, config, ... }: {
+    nodes.machine = { lib, config, ... }: {
       imports = let
         mkVMConf = name: val: val.machine // { key = "${name}-config"; };
         vmConfigs = mapAttrsToList mkVMConf vms;
diff --git a/nixos/tests/web-apps/netbox.nix b/nixos/tests/web-apps/netbox.nix
new file mode 100644
index 0000000000000..95f24029ec928
--- /dev/null
+++ b/nixos/tests/web-apps/netbox.nix
@@ -0,0 +1,30 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "netbox";
+
+  meta = with lib.maintainers; {
+    maintainers = [ n0emis ];
+  };
+
+  machine = { ... }: {
+    services.netbox = {
+      enable = true;
+      secretKeyFile = pkgs.writeText "secret" ''
+        abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("netbox.target")
+    machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening")
+
+    with subtest("Home screen loads"):
+        machine.succeed(
+            "curl -sSfL http://[::1]:8001 | grep '<title>Home | NetBox</title>'"
+        )
+
+    with subtest("Staticfiles are generated"):
+        machine.succeed("test -e /var/lib/netbox/static/netbox.js")
+  '';
+})
diff --git a/nixos/tests/web-servers/unit-php.nix b/nixos/tests/web-servers/unit-php.nix
index 00512b506cc2e..5bef7fab3efff 100644
--- a/nixos/tests/web-servers/unit-php.nix
+++ b/nixos/tests/web-servers/unit-php.nix
@@ -6,7 +6,7 @@ in {
   name = "unit-php-test";
   meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
 
-  machine = { config, lib, pkgs, ... }: {
+  nodes.machine = { config, lib, pkgs, ... }: {
     services.unit = {
       enable = true;
       config = pkgs.lib.strings.toJSON {
diff --git a/nixos/tests/wiki-js.nix b/nixos/tests/wiki-js.nix
index 783887d2dcaa9..c3541be5d8b52 100644
--- a/nixos/tests/wiki-js.nix
+++ b/nixos/tests/wiki-js.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     maintainers = [ ma27 ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     virtualisation.memorySize = 2048;
     services.wiki-js = {
       enable = true;
diff --git a/nixos/tests/wine.nix b/nixos/tests/wine.nix
index 8135cb90a5914..8a64c3179c518 100644
--- a/nixos/tests/wine.nix
+++ b/nixos/tests/wine.nix
@@ -15,7 +15,7 @@ let
       inherit name;
       meta = with pkgs.lib.maintainers; { maintainers = [ chkno ]; };
 
-      machine = { pkgs, ... }: {
+      nodes.machine = { pkgs, ... }: {
         environment.systemPackages = [ pkgs."${packageSet}"."${variant}" ];
         virtualisation.diskSize = 800;
       };
diff --git a/nixos/tests/wmderland.nix b/nixos/tests/wmderland.nix
index 6de0cd9212eea..ebfd443763e1e 100644
--- a/nixos/tests/wmderland.nix
+++ b/nixos/tests/wmderland.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ takagiy ];
   };
 
-  machine = { lib, ... }: {
+  nodes.machine = { lib, ... }: {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
     test-support.displayManager.auto.user = "alice";
     services.xserver.displayManager.defaultSession = lib.mkForce "none+wmderland";
diff --git a/nixos/tests/wpa_supplicant.nix b/nixos/tests/wpa_supplicant.nix
index 40d934b8e1db2..a05a79e8367d9 100644
--- a/nixos/tests/wpa_supplicant.nix
+++ b/nixos/tests/wpa_supplicant.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...}:
     maintainers = [ rnhmjoj ];
   };
 
-  machine = { ... }: {
+  nodes.machine = { ... }: {
     imports = [ ../modules/profiles/minimal.nix ];
 
     # add a virtual wlan interface
diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix
index 9051deebae76e..31f00f77c40d4 100644
--- a/nixos/tests/xfce.nix
+++ b/nixos/tests/xfce.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "xfce";
 
-  machine =
+  nodes.machine =
     { pkgs, ... }:
 
     {
diff --git a/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix
index a2fb38e53bd15..aa55f0e3ca6f2 100644
--- a/nixos/tests/xmonad.nix
+++ b/nixos/tests/xmonad.nix
@@ -55,7 +55,7 @@ in {
     maintainers = [ nequissimus ivanbrennan ];
   };
 
-  machine = { pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
     test-support.displayManager.auto.user = "alice";
     services.xserver.displayManager.defaultSession = "none+xmonad";
diff --git a/nixos/tests/xterm.nix b/nixos/tests/xterm.nix
index 4ee31139ab52b..745d33e8a0d53 100644
--- a/nixos/tests/xterm.nix
+++ b/nixos/tests/xterm.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     maintainers = [ nequissimus ];
   };
 
-  machine = { pkgs, ... }:
+  nodes.machine = { pkgs, ... }:
     {
       imports = [ ./common/x11.nix ];
       services.xserver.desktopManager.xterm.enable = false;
diff --git a/nixos/tests/yabar.nix b/nixos/tests/yabar.nix
index c2431e556c378..ff7a47ae63709 100644
--- a/nixos/tests/yabar.nix
+++ b/nixos/tests/yabar.nix
@@ -8,7 +8,7 @@ with lib;
     maintainers = [ ];
   };
 
-  machine = {
+  nodes.machine = {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
 
     test-support.displayManager.auto.user = "bob";
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index d25090403e5fa..bf0165b88162d 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -18,7 +18,7 @@ let
         maintainers = [ adisbladis ];
       };
 
-      machine = { pkgs, lib, ... }:
+      nodes.machine = { pkgs, lib, ... }:
         let
           usersharePath = "/var/lib/samba/usershares";
         in {
diff --git a/nixos/tests/zigbee2mqtt.nix b/nixos/tests/zigbee2mqtt.nix
index 98aadbb699bdf..1592202fb3a76 100644
--- a/nixos/tests/zigbee2mqtt.nix
+++ b/nixos/tests/zigbee2mqtt.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }:
 
   {
-    machine = { pkgs, ... }:
+    nodes.machine = { pkgs, ... }:
       {
         services.zigbee2mqtt = {
           enable = true;
diff --git a/nixos/tests/zoneminder.nix b/nixos/tests/zoneminder.nix
index a4e1a05ec0ee4..3c97bc8282d22 100644
--- a/nixos/tests/zoneminder.nix
+++ b/nixos/tests/zoneminder.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix ({ lib, ...}:
   name = "zoneminder";
   meta.maintainers = with lib.maintainers; [ danielfullmer ];
 
-  machine = { ... }:
+  nodes.machine = { ... }:
   {
     services.zoneminder = {
       enable = true;
diff --git a/nixos/tests/zrepl.nix b/nixos/tests/zrepl.nix
new file mode 100644
index 0000000000000..85dd834a6aafb
--- /dev/null
+++ b/nixos/tests/zrepl.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix (
+  {
+    nodes.host = {config, pkgs, ...}: {
+      config = {
+        # Prerequisites for ZFS and tests.
+        boot.supportedFilesystems = [ "zfs" ];
+        environment.systemPackages = [ pkgs.zrepl ];
+        networking.hostId = "deadbeef";
+        services.zrepl = {
+          enable = true;
+          settings = {
+            # Enable Prometheus output for status assertions.
+            global.monitoring = [{
+              type = "prometheus";
+              listen = ":9811";
+            }];
+            # Create a periodic snapshot job for an ephemeral zpool.
+            jobs = [{
+              name = "snap_test";
+              type = "snap";
+
+              filesystems."test" = true;
+              snapshotting = {
+                type = "periodic";
+                prefix = "zrepl_";
+                interval = "1s";
+              };
+
+              pruning.keep = [{
+                type = "last_n";
+                count = 8;
+              }];
+            }];
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      with subtest("Wait for zrepl and network ready"):
+          host.wait_for_unit("network-online.target")
+          host.wait_for_unit("zrepl.service")
+
+      with subtest("Create test zpool"):
+          # ZFS requires 64MiB minimum pool size.
+          host.succeed("fallocate -l 64MiB /root/zpool.img")
+          host.succeed("zpool create test /root/zpool.img")
+
+      with subtest("Check for completed zrepl snapshot"):
+          # zrepl periodic snapshot job creates a snapshot with this prefix.
+          host.wait_until_succeeds("zfs list -t snapshot | grep -q zrepl_")
+
+      with subtest("Verify HTTP monitoring server is configured"):
+          out = host.succeed("curl -f localhost:9811/metrics")
+
+          assert (
+              "zrepl_version_daemon" in out
+          ), "zrepl version metric was not found in Prometheus output"
+
+          assert (
+              "zrepl_zfs_snapshot_duration_count{filesystem=\"test\"}" in out
+          ), "zrepl snapshot counter for test was not found in Prometheus output"
+    '';
+  })