about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/tests')
-rw-r--r--nixpkgs/nixos/tests/3proxy.nix188
-rw-r--r--nixpkgs/nixos/tests/aaaaxy.nix29
-rw-r--r--nixpkgs/nixos/tests/acme-dns.nix50
-rw-r--r--nixpkgs/nixos/tests/acme.nix716
-rw-r--r--nixpkgs/nixos/tests/activation/etc-overlay-immutable.nix36
-rw-r--r--nixpkgs/nixos/tests/activation/etc-overlay-mutable.nix36
-rw-r--r--nixpkgs/nixos/tests/activation/nix-channel.nix26
-rw-r--r--nixpkgs/nixos/tests/activation/perlless.nix24
-rw-r--r--nixpkgs/nixos/tests/activation/var.nix18
-rw-r--r--nixpkgs/nixos/tests/adguardhome.nix136
-rw-r--r--nixpkgs/nixos/tests/aesmd.nix102
-rw-r--r--nixpkgs/nixos/tests/agda.nix50
-rw-r--r--nixpkgs/nixos/tests/airsonic.nix29
-rw-r--r--nixpkgs/nixos/tests/akkoma.nix124
-rw-r--r--nixpkgs/nixos/tests/alice-lg.nix44
-rw-r--r--nixpkgs/nixos/tests/all-terminfo.nix35
-rw-r--r--nixpkgs/nixos/tests/all-tests.nix999
-rw-r--r--nixpkgs/nixos/tests/alps.nix108
-rw-r--r--nixpkgs/nixos/tests/amazon-init-shell.nix40
-rw-r--r--nixpkgs/nixos/tests/amazon-ssm-agent.nix17
-rw-r--r--nixpkgs/nixos/tests/amd-sev.nix56
-rw-r--r--nixpkgs/nixos/tests/anbox.nix36
-rw-r--r--nixpkgs/nixos/tests/angie-api.nix148
-rw-r--r--nixpkgs/nixos/tests/anki-sync-server.nix71
-rw-r--r--nixpkgs/nixos/tests/anuko-time-tracker.nix17
-rw-r--r--nixpkgs/nixos/tests/apache_datasketches.nix29
-rw-r--r--nixpkgs/nixos/tests/apcupsd.nix41
-rw-r--r--nixpkgs/nixos/tests/apfs.nix65
-rw-r--r--nixpkgs/nixos/tests/apparmor.nix85
-rw-r--r--nixpkgs/nixos/tests/appliance-repart-image.nix111
-rw-r--r--nixpkgs/nixos/tests/archi.nix31
-rw-r--r--nixpkgs/nixos/tests/atd.nix31
-rw-r--r--nixpkgs/nixos/tests/atop.nix226
-rw-r--r--nixpkgs/nixos/tests/atuin.nix66
-rw-r--r--nixpkgs/nixos/tests/audiobookshelf.nix23
-rw-r--r--nixpkgs/nixos/tests/auth-mysql.nix178
-rw-r--r--nixpkgs/nixos/tests/authelia.nix169
-rw-r--r--nixpkgs/nixos/tests/avahi.nix79
-rw-r--r--nixpkgs/nixos/tests/ayatana-indicators.nix111
-rw-r--r--nixpkgs/nixos/tests/babeld.nix138
-rw-r--r--nixpkgs/nixos/tests/bazarr.nix24
-rw-r--r--nixpkgs/nixos/tests/bcachefs.nix32
-rw-r--r--nixpkgs/nixos/tests/beanstalkd.nix49
-rw-r--r--nixpkgs/nixos/tests/bees.nix62
-rw-r--r--nixpkgs/nixos/tests/binary-cache.nix60
-rw-r--r--nixpkgs/nixos/tests/bind.nix28
-rw-r--r--nixpkgs/nixos/tests/bird.nix129
-rw-r--r--nixpkgs/nixos/tests/birdwatcher.nix94
-rw-r--r--nixpkgs/nixos/tests/bitcoind.nix48
-rw-r--r--nixpkgs/nixos/tests/bittorrent.nix168
-rw-r--r--nixpkgs/nixos/tests/blockbook-frontend.nix28
-rw-r--r--nixpkgs/nixos/tests/blocky.nix34
-rw-r--r--nixpkgs/nixos/tests/boot-stage1.nix164
-rw-r--r--nixpkgs/nixos/tests/boot.nix148
-rw-r--r--nixpkgs/nixos/tests/bootspec.nix201
-rw-r--r--nixpkgs/nixos/tests/borgbackup.nix230
-rw-r--r--nixpkgs/nixos/tests/botamusique.nix51
-rw-r--r--nixpkgs/nixos/tests/bpf.nix36
-rw-r--r--nixpkgs/nixos/tests/bpftune.nix20
-rw-r--r--nixpkgs/nixos/tests/breitbandmessung.nix33
-rw-r--r--nixpkgs/nixos/tests/brscan5.nix43
-rw-r--r--nixpkgs/nixos/tests/btrbk-doas.nix114
-rw-r--r--nixpkgs/nixos/tests/btrbk-no-timer.nix37
-rw-r--r--nixpkgs/nixos/tests/btrbk-section-order.nix56
-rw-r--r--nixpkgs/nixos/tests/btrbk.nix111
-rw-r--r--nixpkgs/nixos/tests/budgie.nix67
-rw-r--r--nixpkgs/nixos/tests/buildbot.nix110
-rw-r--r--nixpkgs/nixos/tests/buildkite-agents.nix29
-rw-r--r--nixpkgs/nixos/tests/c2fmzq.nix82
-rw-r--r--nixpkgs/nixos/tests/caddy.nix103
-rw-r--r--nixpkgs/nixos/tests/cadvisor.nix32
-rw-r--r--nixpkgs/nixos/tests/cage.nix38
-rw-r--r--nixpkgs/nixos/tests/cagebreak.nix65
-rw-r--r--nixpkgs/nixos/tests/calibre-server.nix104
-rw-r--r--nixpkgs/nixos/tests/calibre-web.nix42
-rw-r--r--nixpkgs/nixos/tests/cassandra.nix132
-rw-r--r--nixpkgs/nixos/tests/castopod.nix87
-rw-r--r--nixpkgs/nixos/tests/ccache.nix24
-rw-r--r--nixpkgs/nixos/tests/centrifugo.nix80
-rw-r--r--nixpkgs/nixos/tests/ceph-multi-node.nix241
-rw-r--r--nixpkgs/nixos/tests/ceph-single-node-bluestore.nix204
-rw-r--r--nixpkgs/nixos/tests/ceph-single-node.nix215
-rw-r--r--nixpkgs/nixos/tests/certmgr.nix155
-rw-r--r--nixpkgs/nixos/tests/cfssl.nix67
-rw-r--r--nixpkgs/nixos/tests/cgit.nix73
-rw-r--r--nixpkgs/nixos/tests/charliecloud.nix43
-rw-r--r--nixpkgs/nixos/tests/chromium.nix269
-rw-r--r--nixpkgs/nixos/tests/chrony-ptp.nix28
-rw-r--r--nixpkgs/nixos/tests/chrony.nix31
-rw-r--r--nixpkgs/nixos/tests/cinnamon-wayland.nix77
-rw-r--r--nixpkgs/nixos/tests/cinnamon.nix88
-rw-r--r--nixpkgs/nixos/tests/cjdns.nix121
-rw-r--r--nixpkgs/nixos/tests/clickhouse.nix32
-rw-r--r--nixpkgs/nixos/tests/cloud-init-hostname.nix46
-rw-r--r--nixpkgs/nixos/tests/cloud-init.nix115
-rw-r--r--nixpkgs/nixos/tests/cloudlog.nix18
-rw-r--r--nixpkgs/nixos/tests/cntr.nix75
-rw-r--r--nixpkgs/nixos/tests/cockpit.nix136
-rw-r--r--nixpkgs/nixos/tests/cockroachdb.nix124
-rw-r--r--nixpkgs/nixos/tests/code-server.nix22
-rw-r--r--nixpkgs/nixos/tests/coder.nix24
-rw-r--r--nixpkgs/nixos/tests/collectd.nix38
-rw-r--r--nixpkgs/nixos/tests/common/acme/client/default.nix16
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/README.md21
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem19
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem27
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/ca.cert.pem20
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/ca.key.pem27
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/default.nix141
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/generate-certs.nix33
-rw-r--r--nixpkgs/nixos/tests/common/acme/server/snakeoil-certs.nix13
-rw-r--r--nixpkgs/nixos/tests/common/auto-format-root-device.nix29
-rw-r--r--nixpkgs/nixos/tests/common/auto.nix55
-rw-r--r--nixpkgs/nixos/tests/common/ec2.nix73
-rw-r--r--nixpkgs/nixos/tests/common/gpg-keyring.nix21
-rw-r--r--nixpkgs/nixos/tests/common/resolver.nix141
-rw-r--r--nixpkgs/nixos/tests/common/user-account.nix15
-rw-r--r--nixpkgs/nixos/tests/common/wayland-cage.nix13
-rw-r--r--nixpkgs/nixos/tests/common/webroot/news-rss.xml27
-rw-r--r--nixpkgs/nixos/tests/common/x11.nix17
-rw-r--r--nixpkgs/nixos/tests/connman.nix77
-rw-r--r--nixpkgs/nixos/tests/consul-template.nix36
-rw-r--r--nixpkgs/nixos/tests/consul.nix239
-rw-r--r--nixpkgs/nixos/tests/containers-bridge.nix99
-rw-r--r--nixpkgs/nixos/tests/containers-custom-pkgs.nix34
-rw-r--r--nixpkgs/nixos/tests/containers-ephemeral.nix54
-rw-r--r--nixpkgs/nixos/tests/containers-extra_veth.nix91
-rw-r--r--nixpkgs/nixos/tests/containers-hosts.nix49
-rw-r--r--nixpkgs/nixos/tests/containers-imperative.nix170
-rw-r--r--nixpkgs/nixos/tests/containers-ip.nix71
-rw-r--r--nixpkgs/nixos/tests/containers-macvlans.nix82
-rw-r--r--nixpkgs/nixos/tests/containers-names.nix37
-rw-r--r--nixpkgs/nixos/tests/containers-nested.nix30
-rw-r--r--nixpkgs/nixos/tests/containers-physical_interfaces.nix131
-rw-r--r--nixpkgs/nixos/tests/containers-portforward.nix59
-rw-r--r--nixpkgs/nixos/tests/containers-reloadable.nix71
-rw-r--r--nixpkgs/nixos/tests/containers-restart_networking.nix113
-rw-r--r--nixpkgs/nixos/tests/containers-tmpfs.nix90
-rw-r--r--nixpkgs/nixos/tests/containers-unified-hierarchy.nix21
-rw-r--r--nixpkgs/nixos/tests/convos.nix27
-rw-r--r--nixpkgs/nixos/tests/corerad.nix92
-rw-r--r--nixpkgs/nixos/tests/coturn.nix34
-rw-r--r--nixpkgs/nixos/tests/couchdb.nix57
-rw-r--r--nixpkgs/nixos/tests/cri-o.nix19
-rw-r--r--nixpkgs/nixos/tests/croc.nix51
-rw-r--r--nixpkgs/nixos/tests/cups-pdf.nix40
-rw-r--r--nixpkgs/nixos/tests/curl-impersonate.nix159
-rw-r--r--nixpkgs/nixos/tests/custom-ca.nix195
-rw-r--r--nixpkgs/nixos/tests/dae.nix33
-rw-r--r--nixpkgs/nixos/tests/darling.nix44
-rw-r--r--nixpkgs/nixos/tests/dconf.nix34
-rw-r--r--nixpkgs/nixos/tests/deconz.nix28
-rw-r--r--nixpkgs/nixos/tests/deepin.nix51
-rw-r--r--nixpkgs/nixos/tests/deluge.nix63
-rw-r--r--nixpkgs/nixos/tests/dex-oidc.nix78
-rw-r--r--nixpkgs/nixos/tests/dhparams.nix130
-rw-r--r--nixpkgs/nixos/tests/disable-installer-tools.nix29
-rw-r--r--nixpkgs/nixos/tests/discourse.nix202
-rw-r--r--nixpkgs/nixos/tests/dnscrypt-proxy2.nix38
-rw-r--r--nixpkgs/nixos/tests/dnscrypt-wrapper/default.nix148
-rw-r--r--nixpkgs/nixos/tests/dnscrypt-wrapper/public.key1
-rw-r--r--nixpkgs/nixos/tests/dnscrypt-wrapper/secret.key1
-rw-r--r--nixpkgs/nixos/tests/dnsdist.nix113
-rw-r--r--nixpkgs/nixos/tests/doas.nix96
-rw-r--r--nixpkgs/nixos/tests/docker-registry.nix61
-rw-r--r--nixpkgs/nixos/tests/docker-rootless.nix41
-rw-r--r--nixpkgs/nixos/tests/docker-tools-cross.nix80
-rw-r--r--nixpkgs/nixos/tests/docker-tools-overlay.nix33
-rw-r--r--nixpkgs/nixos/tests/docker-tools.nix608
-rw-r--r--nixpkgs/nixos/tests/docker.nix53
-rw-r--r--nixpkgs/nixos/tests/documize.nix62
-rw-r--r--nixpkgs/nixos/tests/doh-proxy-rust.nix41
-rw-r--r--nixpkgs/nixos/tests/dokuwiki.nix161
-rw-r--r--nixpkgs/nixos/tests/dolibarr.nix59
-rw-r--r--nixpkgs/nixos/tests/domination.nix26
-rw-r--r--nixpkgs/nixos/tests/dovecot.nix82
-rw-r--r--nixpkgs/nixos/tests/drawterm.nix58
-rw-r--r--nixpkgs/nixos/tests/drbd.nix87
-rw-r--r--nixpkgs/nixos/tests/dublin-traceroute.nix63
-rw-r--r--nixpkgs/nixos/tests/early-mount-options.nix19
-rw-r--r--nixpkgs/nixos/tests/earlyoom.nix16
-rw-r--r--nixpkgs/nixos/tests/ec2.nix156
-rw-r--r--nixpkgs/nixos/tests/ecryptfs.nix85
-rw-r--r--nixpkgs/nixos/tests/elk.nix276
-rw-r--r--nixpkgs/nixos/tests/emacs-daemon.nix48
-rw-r--r--nixpkgs/nixos/tests/empty-file0
-rw-r--r--nixpkgs/nixos/tests/endlessh-go.nix58
-rw-r--r--nixpkgs/nixos/tests/endlessh.nix43
-rw-r--r--nixpkgs/nixos/tests/engelsystem.nix41
-rw-r--r--nixpkgs/nixos/tests/enlightenment.nix96
-rw-r--r--nixpkgs/nixos/tests/env.nix36
-rw-r--r--nixpkgs/nixos/tests/envfs.nix42
-rw-r--r--nixpkgs/nixos/tests/envoy.nix54
-rw-r--r--nixpkgs/nixos/tests/ergo.nix18
-rw-r--r--nixpkgs/nixos/tests/ergochat.nix97
-rw-r--r--nixpkgs/nixos/tests/eris-server.nix23
-rw-r--r--nixpkgs/nixos/tests/esphome.nix40
-rw-r--r--nixpkgs/nixos/tests/etcd-cluster.nix157
-rw-r--r--nixpkgs/nixos/tests/etcd.nix25
-rw-r--r--nixpkgs/nixos/tests/etebase-server.nix50
-rw-r--r--nixpkgs/nixos/tests/etesync-dav.nix21
-rw-r--r--nixpkgs/nixos/tests/evcc.nix96
-rw-r--r--nixpkgs/nixos/tests/fail2ban.nix18
-rw-r--r--nixpkgs/nixos/tests/fakeroute.nix22
-rw-r--r--nixpkgs/nixos/tests/fancontrol.nix34
-rw-r--r--nixpkgs/nixos/tests/fanout.nix30
-rw-r--r--nixpkgs/nixos/tests/fastnetmon-advanced.nix65
-rw-r--r--nixpkgs/nixos/tests/fcitx5/default.nix165
-rw-r--r--nixpkgs/nixos/tests/fenics.nix49
-rw-r--r--nixpkgs/nixos/tests/ferm.nix77
-rw-r--r--nixpkgs/nixos/tests/ferretdb.nix64
-rw-r--r--nixpkgs/nixos/tests/filesystems-overlayfs.nix89
-rw-r--r--nixpkgs/nixos/tests/firefox.nix123
-rw-r--r--nixpkgs/nixos/tests/firejail.nix91
-rw-r--r--nixpkgs/nixos/tests/firewall.nix68
-rw-r--r--nixpkgs/nixos/tests/fish.nix24
-rw-r--r--nixpkgs/nixos/tests/flannel.nix57
-rw-r--r--nixpkgs/nixos/tests/fluentd.nix49
-rw-r--r--nixpkgs/nixos/tests/fluidd.nix19
-rw-r--r--nixpkgs/nixos/tests/fontconfig-default-fonts.nix32
-rw-r--r--nixpkgs/nixos/tests/forgejo.nix178
-rw-r--r--nixpkgs/nixos/tests/freenet.nix19
-rw-r--r--nixpkgs/nixos/tests/freeswitch.nix29
-rw-r--r--nixpkgs/nixos/tests/freetube.nix43
-rw-r--r--nixpkgs/nixos/tests/freshrss-http-auth.nix20
-rw-r--r--nixpkgs/nixos/tests/freshrss-pgsql.nix46
-rw-r--r--nixpkgs/nixos/tests/freshrss-sqlite.nix20
-rw-r--r--nixpkgs/nixos/tests/frigate.nix65
-rw-r--r--nixpkgs/nixos/tests/frp.nix85
-rw-r--r--nixpkgs/nixos/tests/frr.nix104
-rw-r--r--nixpkgs/nixos/tests/fsck.nix45
-rw-r--r--nixpkgs/nixos/tests/fscrypt.nix50
-rw-r--r--nixpkgs/nixos/tests/ft2-clone.nix31
-rw-r--r--nixpkgs/nixos/tests/garage/basic.nix98
-rw-r--r--nixpkgs/nixos/tests/garage/default.nix54
-rw-r--r--nixpkgs/nixos/tests/garage/with-3node-replication.nix121
-rw-r--r--nixpkgs/nixos/tests/gemstash.nix51
-rw-r--r--nixpkgs/nixos/tests/geoserver.nix79
-rw-r--r--nixpkgs/nixos/tests/gerrit.nix54
-rw-r--r--nixpkgs/nixos/tests/geth.nix45
-rw-r--r--nixpkgs/nixos/tests/ghostunnel.nix104
-rw-r--r--nixpkgs/nixos/tests/git/hub.nix17
-rw-r--r--nixpkgs/nixos/tests/gitdaemon.nix74
-rw-r--r--nixpkgs/nixos/tests/gitea.nix165
-rw-r--r--nixpkgs/nixos/tests/github-runner.nix37
-rw-r--r--nixpkgs/nixos/tests/gitlab.nix437
-rw-r--r--nixpkgs/nixos/tests/gitolite-fcgiwrap.nix93
-rw-r--r--nixpkgs/nixos/tests/gitolite.nix138
-rw-r--r--nixpkgs/nixos/tests/glusterfs.nix68
-rw-r--r--nixpkgs/nixos/tests/gnome-extensions.nix151
-rw-r--r--nixpkgs/nixos/tests/gnome-flashback.nix52
-rw-r--r--nixpkgs/nixos/tests/gnome-xorg.nix99
-rw-r--r--nixpkgs/nixos/tests/gnome.nix93
-rw-r--r--nixpkgs/nixos/tests/gns3-server.nix55
-rw-r--r--nixpkgs/nixos/tests/gnupg.nix118
-rw-r--r--nixpkgs/nixos/tests/go-camo.nix30
-rw-r--r--nixpkgs/nixos/tests/go-neb.nix44
-rw-r--r--nixpkgs/nixos/tests/gobgpd.nix71
-rw-r--r--nixpkgs/nixos/tests/gocd-agent.nix48
-rw-r--r--nixpkgs/nixos/tests/gocd-server.nix28
-rw-r--r--nixpkgs/nixos/tests/gollum.nix14
-rw-r--r--nixpkgs/nixos/tests/gonic.nix18
-rw-r--r--nixpkgs/nixos/tests/google-oslogin/default.nix73
-rw-r--r--nixpkgs/nixos/tests/google-oslogin/server.nix27
-rwxr-xr-xnixpkgs/nixos/tests/google-oslogin/server.py145
-rw-r--r--nixpkgs/nixos/tests/goss.nix53
-rw-r--r--nixpkgs/nixos/tests/gotify-server.nix50
-rw-r--r--nixpkgs/nixos/tests/grafana-agent.nix32
-rw-r--r--nixpkgs/nixos/tests/grafana/basic.nix142
-rw-r--r--nixpkgs/nixos/tests/grafana/default.nix9
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/contact-points.yaml9
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/dashboards.yaml6
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/datasources.yaml7
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/default.nix256
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/mute-timings.yaml4
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/policies.yaml4
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/rules.yaml36
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/templates.yaml5
-rw-r--r--nixpkgs/nixos/tests/grafana/provision/test_dashboard.json47
-rw-r--r--nixpkgs/nixos/tests/graphite.nix36
-rw-r--r--nixpkgs/nixos/tests/graylog.nix114
-rw-r--r--nixpkgs/nixos/tests/grocy.nix73
-rw-r--r--nixpkgs/nixos/tests/grow-partition.nix83
-rw-r--r--nixpkgs/nixos/tests/grub.nix60
-rw-r--r--nixpkgs/nixos/tests/guacamole-server.nix21
-rw-r--r--nixpkgs/nixos/tests/guix/basic.nix42
-rw-r--r--nixpkgs/nixos/tests/guix/default.nix8
-rw-r--r--nixpkgs/nixos/tests/guix/publish.nix96
-rw-r--r--nixpkgs/nixos/tests/guix/scripts/add-existing-files-to-store.scm52
-rw-r--r--nixpkgs/nixos/tests/guix/scripts/create-file-to-store.scm8
-rw-r--r--nixpkgs/nixos/tests/gvisor.nix43
-rw-r--r--nixpkgs/nixos/tests/hadoop/default.nix8
-rw-r--r--nixpkgs/nixos/tests/hadoop/hadoop.nix255
-rw-r--r--nixpkgs/nixos/tests/hadoop/hbase.nix109
-rw-r--r--nixpkgs/nixos/tests/hadoop/hdfs.nix83
-rw-r--r--nixpkgs/nixos/tests/hadoop/yarn.nix45
-rw-r--r--nixpkgs/nixos/tests/haka.nix24
-rw-r--r--nixpkgs/nixos/tests/haproxy.nix124
-rw-r--r--nixpkgs/nixos/tests/hardened.nix105
-rw-r--r--nixpkgs/nixos/tests/harmonia.nix40
-rw-r--r--nixpkgs/nixos/tests/haste-server.nix23
-rw-r--r--nixpkgs/nixos/tests/hbase.nix30
-rw-r--r--nixpkgs/nixos/tests/hddfancontrol.nix44
-rw-r--r--nixpkgs/nixos/tests/headscale.nix82
-rw-r--r--nixpkgs/nixos/tests/hedgedoc.nix96
-rw-r--r--nixpkgs/nixos/tests/herbstluftwm.nix37
-rw-r--r--nixpkgs/nixos/tests/hibernate.nix55
-rw-r--r--nixpkgs/nixos/tests/hitch/default.nix33
-rw-r--r--nixpkgs/nixos/tests/hitch/example.pem53
-rw-r--r--nixpkgs/nixos/tests/hitch/example/index.txt1
-rw-r--r--nixpkgs/nixos/tests/hledger-web.nix50
-rw-r--r--nixpkgs/nixos/tests/hocker-fetchdocker/default.nix16
-rw-r--r--nixpkgs/nixos/tests/hocker-fetchdocker/hello-world-container.nix19
-rw-r--r--nixpkgs/nixos/tests/hocker-fetchdocker/machine.nix26
-rw-r--r--nixpkgs/nixos/tests/hockeypuck.nix63
-rw-r--r--nixpkgs/nixos/tests/home-assistant.nix241
-rw-r--r--nixpkgs/nixos/tests/homepage-dashboard.nix14
-rw-r--r--nixpkgs/nixos/tests/honk.nix32
-rw-r--r--nixpkgs/nixos/tests/hostname.nix73
-rw-r--r--nixpkgs/nixos/tests/hound.nix59
-rw-r--r--nixpkgs/nixos/tests/hydra/common.nix48
-rwxr-xr-xnixpkgs/nixos/tests/hydra/create-trivial-project.sh59
-rw-r--r--nixpkgs/nixos/tests/hydra/default.nix59
-rw-r--r--nixpkgs/nixos/tests/i3wm.nix46
-rw-r--r--nixpkgs/nixos/tests/icingaweb2.nix71
-rw-r--r--nixpkgs/nixos/tests/iftop.nix31
-rw-r--r--nixpkgs/nixos/tests/image-contents.nix62
-rw-r--r--nixpkgs/nixos/tests/incron.nix52
-rw-r--r--nixpkgs/nixos/tests/incus/container.nix105
-rw-r--r--nixpkgs/nixos/tests/incus/default.nix18
-rw-r--r--nixpkgs/nixos/tests/incus/lxd-to-incus.nix110
-rw-r--r--nixpkgs/nixos/tests/incus/preseed.nix62
-rw-r--r--nixpkgs/nixos/tests/incus/socket-activated.nix28
-rw-r--r--nixpkgs/nixos/tests/incus/ui.nix63
-rw-r--r--nixpkgs/nixos/tests/incus/virtual-machine.nix60
-rw-r--r--nixpkgs/nixos/tests/influxdb.nix40
-rw-r--r--nixpkgs/nixos/tests/influxdb2.nix225
-rw-r--r--nixpkgs/nixos/tests/initrd-luks-empty-passphrase.nix105
-rw-r--r--nixpkgs/nixos/tests/initrd-network-openvpn/default.nix165
-rw-r--r--nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn30
-rw-r--r--nixpkgs/nixos/tests/initrd-network-openvpn/shared.key21
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/default.nix73
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/generate-keys.nix10
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/id_ed255197
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/id_ed25519.pub1
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key7
-rw-r--r--nixpkgs/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key.pub1
-rw-r--r--nixpkgs/nixos/tests/initrd-network.nix33
-rw-r--r--nixpkgs/nixos/tests/initrd-secrets-changing.nix57
-rw-r--r--nixpkgs/nixos/tests/initrd-secrets.nix41
-rw-r--r--nixpkgs/nixos/tests/input-remapper.nix53
-rw-r--r--nixpkgs/nixos/tests/inspircd.nix93
-rw-r--r--nixpkgs/nixos/tests/installed-tests/appstream-qt.nix9
-rw-r--r--nixpkgs/nixos/tests/installed-tests/appstream.nix9
-rw-r--r--nixpkgs/nixos/tests/installed-tests/colord.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/default.nix112
-rw-r--r--nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix15
-rw-r--r--nixpkgs/nixos/tests/installed-tests/flatpak.nix18
-rw-r--r--nixpkgs/nixos/tests/installed-tests/fwupd.nix12
-rw-r--r--nixpkgs/nixos/tests/installed-tests/gcab.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix13
-rw-r--r--nixpkgs/nixos/tests/installed-tests/geocode-glib.nix13
-rw-r--r--nixpkgs/nixos/tests/installed-tests/gjs.nix12
-rw-r--r--nixpkgs/nixos/tests/installed-tests/glib-networking.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/glib-testing.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/gnome-photos.nix35
-rw-r--r--nixpkgs/nixos/tests/installed-tests/graphene.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/gsconnect.nix7
-rw-r--r--nixpkgs/nixos/tests/installed-tests/ibus.nix17
-rw-r--r--nixpkgs/nixos/tests/installed-tests/json-glib.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/libgdata.nix11
-rw-r--r--nixpkgs/nixos/tests/installed-tests/libjcat.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/libxmlb.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/malcontent.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/ostree.nix12
-rw-r--r--nixpkgs/nixos/tests/installed-tests/pipewire.nix5
-rw-r--r--nixpkgs/nixos/tests/installed-tests/upower.nix9
-rw-r--r--nixpkgs/nixos/tests/installed-tests/xdg-desktop-portal.nix9
-rw-r--r--nixpkgs/nixos/tests/installer-systemd-stage-1.nix42
-rw-r--r--nixpkgs/nixos/tests/installer.nix1429
-rw-r--r--nixpkgs/nixos/tests/installer/flake.nix20
-rw-r--r--nixpkgs/nixos/tests/intune.nix56
-rw-r--r--nixpkgs/nixos/tests/invidious.nix124
-rw-r--r--nixpkgs/nixos/tests/invoiceplane.nix106
-rw-r--r--nixpkgs/nixos/tests/iodine.nix64
-rw-r--r--nixpkgs/nixos/tests/ipv6.nix130
-rw-r--r--nixpkgs/nixos/tests/iscsi-multipath-root.nix267
-rw-r--r--nixpkgs/nixos/tests/iscsi-root.nix161
-rw-r--r--nixpkgs/nixos/tests/isso.nix30
-rw-r--r--nixpkgs/nixos/tests/jackett.nix17
-rw-r--r--nixpkgs/nixos/tests/jellyfin.nix155
-rw-r--r--nixpkgs/nixos/tests/jenkins-cli.nix30
-rw-r--r--nixpkgs/nixos/tests/jenkins.nix123
-rw-r--r--nixpkgs/nixos/tests/jibri.nix66
-rw-r--r--nixpkgs/nixos/tests/jirafeau.nix20
-rw-r--r--nixpkgs/nixos/tests/jitsi-meet.nix68
-rw-r--r--nixpkgs/nixos/tests/jool.nix220
-rw-r--r--nixpkgs/nixos/tests/k3s/default.nix13
-rw-r--r--nixpkgs/nixos/tests/k3s/multi-node.nix183
-rw-r--r--nixpkgs/nixos/tests/k3s/single-node.nix85
-rw-r--r--nixpkgs/nixos/tests/kafka.nix115
-rw-r--r--nixpkgs/nixos/tests/kanidm.nix129
-rw-r--r--nixpkgs/nixos/tests/karma.nix84
-rw-r--r--nixpkgs/nixos/tests/kavita.nix36
-rw-r--r--nixpkgs/nixos/tests/kbd-setfont-decompress.nix21
-rw-r--r--nixpkgs/nixos/tests/kbd-update-search-paths-patch.nix19
-rw-r--r--nixpkgs/nixos/tests/kea.nix186
-rw-r--r--nixpkgs/nixos/tests/keepalived.nix43
-rw-r--r--nixpkgs/nixos/tests/keepassxc.nix72
-rw-r--r--nixpkgs/nixos/tests/kerberos/default.nix7
-rw-r--r--nixpkgs/nixos/tests/kerberos/heimdal.nix47
-rw-r--r--nixpkgs/nixos/tests/kerberos/mit.nix46
-rw-r--r--nixpkgs/nixos/tests/kernel-generic.nix51
-rw-r--r--nixpkgs/nixos/tests/kernel-latest-ath-user-regd.nix17
-rw-r--r--nixpkgs/nixos/tests/kernel-rust.nix43
-rw-r--r--nixpkgs/nixos/tests/keter.nix43
-rw-r--r--nixpkgs/nixos/tests/kexec.nix50
-rw-r--r--nixpkgs/nixos/tests/keycloak.nix183
-rw-r--r--nixpkgs/nixos/tests/keyd.nix89
-rw-r--r--nixpkgs/nixos/tests/keymap.nix233
-rw-r--r--nixpkgs/nixos/tests/knot.nix203
-rw-r--r--nixpkgs/nixos/tests/komga.nix20
-rw-r--r--nixpkgs/nixos/tests/krb5/default.nix4
-rw-r--r--nixpkgs/nixos/tests/krb5/example-config.nix118
-rw-r--r--nixpkgs/nixos/tests/ksm.nix22
-rw-r--r--nixpkgs/nixos/tests/kthxbye.nix110
-rw-r--r--nixpkgs/nixos/tests/kubernetes/base.nix107
-rw-r--r--nixpkgs/nixos/tests/kubernetes/default.nix13
-rw-r--r--nixpkgs/nixos/tests/kubernetes/dns.nix159
-rw-r--r--nixpkgs/nixos/tests/kubernetes/rbac.nix168
-rw-r--r--nixpkgs/nixos/tests/kubo/default.nix7
-rw-r--r--nixpkgs/nixos/tests/kubo/kubo-fuse.nix42
-rw-r--r--nixpkgs/nixos/tests/kubo/kubo.nix60
-rw-r--r--nixpkgs/nixos/tests/ladybird.nix30
-rw-r--r--nixpkgs/nixos/tests/languagetool.nix19
-rw-r--r--nixpkgs/nixos/tests/lanraragi.nix38
-rw-r--r--nixpkgs/nixos/tests/leaps.nix32
-rw-r--r--nixpkgs/nixos/tests/legit.nix54
-rw-r--r--nixpkgs/nixos/tests/lemmy.nix100
-rw-r--r--nixpkgs/nixos/tests/libinput.nix38
-rw-r--r--nixpkgs/nixos/tests/libreddit.nix19
-rw-r--r--nixpkgs/nixos/tests/librenms.nix108
-rw-r--r--nixpkgs/nixos/tests/libresprite.nix30
-rw-r--r--nixpkgs/nixos/tests/libreswan.nix136
-rw-r--r--nixpkgs/nixos/tests/libuiohook.nix21
-rw-r--r--nixpkgs/nixos/tests/libvirtd.nix68
-rw-r--r--nixpkgs/nixos/tests/lidarr.nix18
-rw-r--r--nixpkgs/nixos/tests/lightdm.nix28
-rw-r--r--nixpkgs/nixos/tests/lighttpd.nix22
-rw-r--r--nixpkgs/nixos/tests/limesurvey.nix26
-rw-r--r--nixpkgs/nixos/tests/listmonk.nix76
-rw-r--r--nixpkgs/nixos/tests/litestream.nix101
-rw-r--r--nixpkgs/nixos/tests/livebook-service.nix45
-rw-r--r--nixpkgs/nixos/tests/lldap.nix26
-rw-r--r--nixpkgs/nixos/tests/locate.nix62
-rw-r--r--nixpkgs/nixos/tests/login.nix68
-rw-r--r--nixpkgs/nixos/tests/logrotate.nix123
-rw-r--r--nixpkgs/nixos/tests/loki.nix56
-rw-r--r--nixpkgs/nixos/tests/lorri/builder.sh3
-rw-r--r--nixpkgs/nixos/tests/lorri/default.nix28
-rw-r--r--nixpkgs/nixos/tests/lorri/fake-shell.nix5
-rw-r--r--nixpkgs/nixos/tests/luks.nix73
-rw-r--r--nixpkgs/nixos/tests/lvm2/default.nix45
-rw-r--r--nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix106
-rw-r--r--nixpkgs/nixos/tests/lvm2/thinpool.nix34
-rw-r--r--nixpkgs/nixos/tests/lvm2/vdo.nix27
-rw-r--r--nixpkgs/nixos/tests/lxd-image-server.nix94
-rw-r--r--nixpkgs/nixos/tests/lxd/container.nix132
-rw-r--r--nixpkgs/nixos/tests/lxd/default.nix12
-rw-r--r--nixpkgs/nixos/tests/lxd/nftables.nix50
-rw-r--r--nixpkgs/nixos/tests/lxd/preseed.nix71
-rw-r--r--nixpkgs/nixos/tests/lxd/ui.nix66
-rw-r--r--nixpkgs/nixos/tests/lxd/virtual-machine.nix64
-rw-r--r--nixpkgs/nixos/tests/maddy/default.nix6
-rw-r--r--nixpkgs/nixos/tests/maddy/tls.nix94
-rw-r--r--nixpkgs/nixos/tests/maddy/unencrypted.nix60
-rw-r--r--nixpkgs/nixos/tests/maestral.nix73
-rw-r--r--nixpkgs/nixos/tests/magic-wormhole-mailbox-server.nix38
-rw-r--r--nixpkgs/nixos/tests/magnetico.nix41
-rw-r--r--nixpkgs/nixos/tests/mailcatcher.nix35
-rw-r--r--nixpkgs/nixos/tests/mailhog.nix24
-rw-r--r--nixpkgs/nixos/tests/mailman.nix73
-rw-r--r--nixpkgs/nixos/tests/make-test-python.nix9
-rw-r--r--nixpkgs/nixos/tests/man.nix100
-rw-r--r--nixpkgs/nixos/tests/mate.nix81
-rw-r--r--nixpkgs/nixos/tests/matomo.nix54
-rw-r--r--nixpkgs/nixos/tests/matrix/appservice-irc.nix225
-rw-r--r--nixpkgs/nixos/tests/matrix/conduit.nix97
-rw-r--r--nixpkgs/nixos/tests/matrix/dendrite.nix101
-rw-r--r--nixpkgs/nixos/tests/matrix/mjolnir.nix176
-rw-r--r--nixpkgs/nixos/tests/matrix/pantalaimon.nix88
-rw-r--r--nixpkgs/nixos/tests/matrix/synapse-workers.nix50
-rw-r--r--nixpkgs/nixos/tests/matrix/synapse.nix218
-rw-r--r--nixpkgs/nixos/tests/mattermost.nix140
-rw-r--r--nixpkgs/nixos/tests/mediamtx.nix57
-rw-r--r--nixpkgs/nixos/tests/mediatomb.nix44
-rw-r--r--nixpkgs/nixos/tests/mediawiki.nix93
-rw-r--r--nixpkgs/nixos/tests/meilisearch.nix61
-rw-r--r--nixpkgs/nixos/tests/memcached.nix24
-rw-r--r--nixpkgs/nixos/tests/merecat.nix28
-rw-r--r--nixpkgs/nixos/tests/metabase.nix19
-rw-r--r--nixpkgs/nixos/tests/mimir.nix50
-rw-r--r--nixpkgs/nixos/tests/mindustry.nix28
-rw-r--r--nixpkgs/nixos/tests/minecraft-server.nix40
-rw-r--r--nixpkgs/nixos/tests/minecraft.nix28
-rw-r--r--nixpkgs/nixos/tests/minidlna.nix40
-rw-r--r--nixpkgs/nixos/tests/miniflux.nix87
-rw-r--r--nixpkgs/nixos/tests/minio.nix72
-rw-r--r--nixpkgs/nixos/tests/miriway.nix125
-rw-r--r--nixpkgs/nixos/tests/misc.nix164
-rw-r--r--nixpkgs/nixos/tests/mobilizon.nix44
-rw-r--r--nixpkgs/nixos/tests/mod_perl.nix53
-rw-r--r--nixpkgs/nixos/tests/molly-brown.nix71
-rw-r--r--nixpkgs/nixos/tests/mongodb.nix50
-rw-r--r--nixpkgs/nixos/tests/moodle.nix22
-rw-r--r--nixpkgs/nixos/tests/moonraker.nix45
-rw-r--r--nixpkgs/nixos/tests/moosefs.nix89
-rw-r--r--nixpkgs/nixos/tests/morph-browser.nix53
-rw-r--r--nixpkgs/nixos/tests/morty.nix30
-rw-r--r--nixpkgs/nixos/tests/mosquitto.nix213
-rw-r--r--nixpkgs/nixos/tests/mpd.nix134
-rw-r--r--nixpkgs/nixos/tests/mpich-example.c21
-rw-r--r--nixpkgs/nixos/tests/mpv.nix26
-rw-r--r--nixpkgs/nixos/tests/mtp.nix109
-rw-r--r--nixpkgs/nixos/tests/multipass.nix37
-rw-r--r--nixpkgs/nixos/tests/mumble.nix89
-rw-r--r--nixpkgs/nixos/tests/munin.nix46
-rw-r--r--nixpkgs/nixos/tests/musescore.nix103
-rw-r--r--nixpkgs/nixos/tests/mutable-users.nix73
-rw-r--r--nixpkgs/nixos/tests/mxisd.nix21
-rw-r--r--nixpkgs/nixos/tests/mympd.nix27
-rw-r--r--nixpkgs/nixos/tests/mysql/common.nix10
-rw-r--r--nixpkgs/nixos/tests/mysql/mariadb-galera.nix250
-rw-r--r--nixpkgs/nixos/tests/mysql/mysql-autobackup.nix53
-rw-r--r--nixpkgs/nixos/tests/mysql/mysql-backup.nix68
-rw-r--r--nixpkgs/nixos/tests/mysql/mysql-replication.nix101
-rw-r--r--nixpkgs/nixos/tests/mysql/mysql.nix151
-rw-r--r--nixpkgs/nixos/tests/mysql/testdb.sql11
-rw-r--r--nixpkgs/nixos/tests/n8n.nix25
-rw-r--r--nixpkgs/nixos/tests/nagios.nix116
-rw-r--r--nixpkgs/nixos/tests/nar-serve.nix50
-rw-r--r--nixpkgs/nixos/tests/nat.nix115
-rw-r--r--nixpkgs/nixos/tests/nats.nix63
-rw-r--r--nixpkgs/nixos/tests/navidrome.nix12
-rw-r--r--nixpkgs/nixos/tests/nbd.nix103
-rw-r--r--nixpkgs/nixos/tests/ncdns.nix93
-rw-r--r--nixpkgs/nixos/tests/ndppd.nix60
-rw-r--r--nixpkgs/nixos/tests/nebula.nix308
-rw-r--r--nixpkgs/nixos/tests/neo4j.nix26
-rw-r--r--nixpkgs/nixos/tests/netbird.nix21
-rw-r--r--nixpkgs/nixos/tests/netdata.nix41
-rw-r--r--nixpkgs/nixos/tests/networking-proxy.nix134
-rw-r--r--nixpkgs/nixos/tests/networking.nix1068
-rw-r--r--nixpkgs/nixos/tests/nextcloud/basic.nix120
-rw-r--r--nixpkgs/nixos/tests/nextcloud/default.nix25
-rw-r--r--nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix126
-rw-r--r--nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix79
-rw-r--r--nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix98
-rw-r--r--nixpkgs/nixos/tests/nexus.nix32
-rw-r--r--nixpkgs/nixos/tests/nfs/default.nix9
-rw-r--r--nixpkgs/nixos/tests/nfs/kerberos.nix136
-rw-r--r--nixpkgs/nixos/tests/nfs/simple.nix95
-rw-r--r--nixpkgs/nixos/tests/nghttpx.nix61
-rw-r--r--nixpkgs/nixos/tests/nginx-auth.nix47
-rw-r--r--nixpkgs/nixos/tests/nginx-etag-compression.nix45
-rw-r--r--nixpkgs/nixos/tests/nginx-etag.nix88
-rw-r--r--nixpkgs/nixos/tests/nginx-globalredirect.nix24
-rw-r--r--nixpkgs/nixos/tests/nginx-http3.nix113
-rw-r--r--nixpkgs/nixos/tests/nginx-modsecurity.nix39
-rw-r--r--nixpkgs/nixos/tests/nginx-moreheaders.nix37
-rw-r--r--nixpkgs/nixos/tests/nginx-njs.nix27
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem20
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem27
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/ca.cert.pem20
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/ca.key.pem27
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/default.nix148
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/generate-certs.nix30
-rw-r--r--nixpkgs/nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix14
-rw-r--r--nixpkgs/nixos/tests/nginx-pubhtml.nix21
-rw-r--r--nixpkgs/nixos/tests/nginx-redirectcode.nix25
-rw-r--r--nixpkgs/nixos/tests/nginx-sso.nix48
-rw-r--r--nixpkgs/nixos/tests/nginx-status-page.nix72
-rw-r--r--nixpkgs/nixos/tests/nginx-tmpdir.nix60
-rw-r--r--nixpkgs/nixos/tests/nginx-unix-socket.nix27
-rw-r--r--nixpkgs/nixos/tests/nginx-variants.nix33
-rw-r--r--nixpkgs/nixos/tests/nginx.nix137
-rw-r--r--nixpkgs/nixos/tests/nitter.nix33
-rw-r--r--nixpkgs/nixos/tests/nix-ld.nix17
-rw-r--r--nixpkgs/nixos/tests/nix-serve-ssh.nix45
-rw-r--r--nixpkgs/nixos/tests/nix-serve.nix22
-rw-r--r--nixpkgs/nixos/tests/nixops/default.nix114
-rw-r--r--nixpkgs/nixos/tests/nixops/legacy/base-configuration.nix31
-rw-r--r--nixpkgs/nixos/tests/nixops/legacy/nixops.nix15
-rw-r--r--nixpkgs/nixos/tests/nixos-generate-config.nix41
-rw-r--r--nixpkgs/nixos/tests/nixos-rebuild-install-bootloader.nix73
-rw-r--r--nixpkgs/nixos/tests/nixos-rebuild-specialisations.nix120
-rw-r--r--nixpkgs/nixos/tests/nixos-rebuild-target-host.nix141
-rw-r--r--nixpkgs/nixos/tests/nixos-test-driver/busybox.nix16
-rw-r--r--nixpkgs/nixos/tests/nixos-test-driver/extra-python-packages.nix13
-rw-r--r--nixpkgs/nixos/tests/nixos-test-driver/lib-extend.nix31
-rw-r--r--nixpkgs/nixos/tests/nixos-test-driver/node-name.nix33
-rw-r--r--nixpkgs/nixos/tests/nixos-test-driver/timeout.nix15
-rw-r--r--nixpkgs/nixos/tests/nixseparatedebuginfod.nix80
-rw-r--r--nixpkgs/nixos/tests/node-red.nix31
-rw-r--r--nixpkgs/nixos/tests/nomad.nix97
-rw-r--r--nixpkgs/nixos/tests/non-default-filesystems.nix172
-rw-r--r--nixpkgs/nixos/tests/non-switchable-system.nix15
-rw-r--r--nixpkgs/nixos/tests/noto-fonts-cjk-qt-default-weight.nix30
-rw-r--r--nixpkgs/nixos/tests/noto-fonts.nix42
-rw-r--r--nixpkgs/nixos/tests/novacomd.nix28
-rw-r--r--nixpkgs/nixos/tests/npmrc.nix22
-rw-r--r--nixpkgs/nixos/tests/nscd.nix142
-rw-r--r--nixpkgs/nixos/tests/nsd.nix109
-rw-r--r--nixpkgs/nixos/tests/ntfy-sh-migration.nix77
-rw-r--r--nixpkgs/nixos/tests/ntfy-sh.nix26
-rw-r--r--nixpkgs/nixos/tests/ntpd-rs.nix51
-rw-r--r--nixpkgs/nixos/tests/nvmetcfg.nix43
-rw-r--r--nixpkgs/nixos/tests/nzbget.nix46
-rw-r--r--nixpkgs/nixos/tests/nzbhydra2.nix14
-rw-r--r--nixpkgs/nixos/tests/oci-containers.nix47
-rw-r--r--nixpkgs/nixos/tests/ocsinventory-agent.nix33
-rw-r--r--nixpkgs/nixos/tests/octoprint.nix61
-rw-r--r--nixpkgs/nixos/tests/odoo.nix26
-rw-r--r--nixpkgs/nixos/tests/oh-my-zsh.nix18
-rw-r--r--nixpkgs/nixos/tests/ombi.nix16
-rw-r--r--nixpkgs/nixos/tests/openarena.nix71
-rw-r--r--nixpkgs/nixos/tests/openldap.nix156
-rw-r--r--nixpkgs/nixos/tests/openresty-lua.nix101
-rw-r--r--nixpkgs/nixos/tests/opensearch.nix47
-rw-r--r--nixpkgs/nixos/tests/opensmtpd-rspamd.nix142
-rw-r--r--nixpkgs/nixos/tests/opensmtpd.nix126
-rw-r--r--nixpkgs/nixos/tests/opensnitch.nix62
-rw-r--r--nixpkgs/nixos/tests/openssh.nix213
-rw-r--r--nixpkgs/nixos/tests/openstack-image.nix98
-rw-r--r--nixpkgs/nixos/tests/opentabletdriver.nix32
-rw-r--r--nixpkgs/nixos/tests/opentelemetry-collector.nix76
-rw-r--r--nixpkgs/nixos/tests/openvscode-server.nix22
-rw-r--r--nixpkgs/nixos/tests/orangefs.nix82
-rw-r--r--nixpkgs/nixos/tests/os-prober.nix133
-rw-r--r--nixpkgs/nixos/tests/osquery.nix52
-rw-r--r--nixpkgs/nixos/tests/osrm-backend.nix57
-rw-r--r--nixpkgs/nixos/tests/outline.nix54
-rw-r--r--nixpkgs/nixos/tests/overlayfs.nix47
-rw-r--r--nixpkgs/nixos/tests/owncast.nix44
-rw-r--r--nixpkgs/nixos/tests/pacemaker.nix110
-rw-r--r--nixpkgs/nixos/tests/packagekit.nix25
-rw-r--r--nixpkgs/nixos/tests/pam/pam-file-contents.nix26
-rw-r--r--nixpkgs/nixos/tests/pam/pam-oath-login.nix108
-rw-r--r--nixpkgs/nixos/tests/pam/pam-u2f.nix26
-rw-r--r--nixpkgs/nixos/tests/pam/pam-ussh.nix70
-rw-r--r--nixpkgs/nixos/tests/pam/test_chfn.py28
-rw-r--r--nixpkgs/nixos/tests/pam/zfs-key.nix83
-rw-r--r--nixpkgs/nixos/tests/pantheon.nix103
-rw-r--r--nixpkgs/nixos/tests/paperless.nix89
-rw-r--r--nixpkgs/nixos/tests/parsedmarc/default.nix230
-rw-r--r--nixpkgs/nixos/tests/pass-secret-service.nix69
-rw-r--r--nixpkgs/nixos/tests/patroni.nix206
-rw-r--r--nixpkgs/nixos/tests/pdns-recursor.nix15
-rw-r--r--nixpkgs/nixos/tests/peerflix.nix23
-rw-r--r--nixpkgs/nixos/tests/peroxide.nix16
-rw-r--r--nixpkgs/nixos/tests/pgadmin4.nix80
-rw-r--r--nixpkgs/nixos/tests/pgbouncer.nix59
-rw-r--r--nixpkgs/nixos/tests/pgjwt.nix34
-rw-r--r--nixpkgs/nixos/tests/pgmanage.nix41
-rw-r--r--nixpkgs/nixos/tests/phosh.nix70
-rw-r--r--nixpkgs/nixos/tests/photoprism.nix23
-rw-r--r--nixpkgs/nixos/tests/php/default.nix16
-rw-r--r--nixpkgs/nixos/tests/php/fpm.nix59
-rw-r--r--nixpkgs/nixos/tests/php/httpd.nix34
-rw-r--r--nixpkgs/nixos/tests/php/pcre.nix52
-rw-r--r--nixpkgs/nixos/tests/pict-rs.nix17
-rw-r--r--nixpkgs/nixos/tests/pinnwand.nix93
-rw-r--r--nixpkgs/nixos/tests/plantuml-server.nix20
-rw-r--r--nixpkgs/nixos/tests/plasma-bigscreen.nix35
-rw-r--r--nixpkgs/nixos/tests/plasma5-systemd-start.nix40
-rw-r--r--nixpkgs/nixos/tests/plasma5.nix67
-rw-r--r--nixpkgs/nixos/tests/plausible.nix52
-rw-r--r--nixpkgs/nixos/tests/please.nix66
-rw-r--r--nixpkgs/nixos/tests/pleroma.nix254
-rw-r--r--nixpkgs/nixos/tests/plikd.nix27
-rw-r--r--nixpkgs/nixos/tests/plotinus.nix28
-rw-r--r--nixpkgs/nixos/tests/podgrab.nix34
-rw-r--r--nixpkgs/nixos/tests/podman/default.nix181
-rw-r--r--nixpkgs/nixos/tests/podman/tls-ghostunnel.nix147
-rw-r--r--nixpkgs/nixos/tests/polaris.nix29
-rw-r--r--nixpkgs/nixos/tests/pomerium.nix109
-rw-r--r--nixpkgs/nixos/tests/portunus.nix18
-rw-r--r--nixpkgs/nixos/tests/postfix-raise-smtpd-tls-security-level.nix41
-rw-r--r--nixpkgs/nixos/tests/postfix.nix77
-rw-r--r--nixpkgs/nixos/tests/postfixadmin.nix31
-rw-r--r--nixpkgs/nixos/tests/postgis.nix30
-rw-r--r--nixpkgs/nixos/tests/postgresql-jit.nix48
-rw-r--r--nixpkgs/nixos/tests/postgresql-wal-receiver.nix119
-rw-r--r--nixpkgs/nixos/tests/postgresql.nix224
-rw-r--r--nixpkgs/nixos/tests/power-profiles-daemon.nix57
-rw-r--r--nixpkgs/nixos/tests/powerdns-admin.nix139
-rw-r--r--nixpkgs/nixos/tests/powerdns.nix62
-rw-r--r--nixpkgs/nixos/tests/pppd.nix64
-rw-r--r--nixpkgs/nixos/tests/predictable-interface-names.nix60
-rw-r--r--nixpkgs/nixos/tests/printing.nix122
-rw-r--r--nixpkgs/nixos/tests/privoxy.nix113
-rw-r--r--nixpkgs/nixos/tests/prometheus-exporters.nix1740
-rw-r--r--nixpkgs/nixos/tests/prometheus.nix349
-rw-r--r--nixpkgs/nixos/tests/promscale.nix60
-rw-r--r--nixpkgs/nixos/tests/prowlarr.nix18
-rw-r--r--nixpkgs/nixos/tests/proxy.nix90
-rw-r--r--nixpkgs/nixos/tests/pt2-clone.nix35
-rw-r--r--nixpkgs/nixos/tests/public-inbox.nix230
-rw-r--r--nixpkgs/nixos/tests/pufferpanel.nix74
-rw-r--r--nixpkgs/nixos/tests/pulseaudio.nix80
-rw-r--r--nixpkgs/nixos/tests/pykms.nix14
-rw-r--r--nixpkgs/nixos/tests/pyload.nix33
-rw-r--r--nixpkgs/nixos/tests/qboot.nix13
-rw-r--r--nixpkgs/nixos/tests/qemu-vm-external-disk-image.nix73
-rw-r--r--nixpkgs/nixos/tests/qemu-vm-restrictnetwork.nix38
-rw-r--r--nixpkgs/nixos/tests/qemu-vm-volatile-root.nix17
-rw-r--r--nixpkgs/nixos/tests/qgis.nix30
-rw-r--r--nixpkgs/nixos/tests/qownnotes.nix82
-rw-r--r--nixpkgs/nixos/tests/quake3.nix95
-rw-r--r--nixpkgs/nixos/tests/quicktun.nix18
-rw-r--r--nixpkgs/nixos/tests/quorum.nix102
-rw-r--r--nixpkgs/nixos/tests/rabbitmq.nix61
-rw-r--r--nixpkgs/nixos/tests/radarr.nix16
-rw-r--r--nixpkgs/nixos/tests/radicale.nix95
-rw-r--r--nixpkgs/nixos/tests/ragnarwm.nix32
-rw-r--r--nixpkgs/nixos/tests/rasdaemon.nix34
-rw-r--r--nixpkgs/nixos/tests/readarr.nix14
-rw-r--r--nixpkgs/nixos/tests/redis.nix44
-rw-r--r--nixpkgs/nixos/tests/redmine.nix44
-rw-r--r--nixpkgs/nixos/tests/restart-by-activation-script.nix73
-rw-r--r--nixpkgs/nixos/tests/restic.nix195
-rw-r--r--nixpkgs/nixos/tests/retroarch.nix49
-rw-r--r--nixpkgs/nixos/tests/rkvm/cert.pem18
-rw-r--r--nixpkgs/nixos/tests/rkvm/default.nix104
-rw-r--r--nixpkgs/nixos/tests/rkvm/key.pem28
-rw-r--r--nixpkgs/nixos/tests/robustirc-bridge.nix29
-rw-r--r--nixpkgs/nixos/tests/rosenpass.nix217
-rw-r--r--nixpkgs/nixos/tests/roundcube.nix31
-rw-r--r--nixpkgs/nixos/tests/rshim.nix25
-rw-r--r--nixpkgs/nixos/tests/rspamd-trainer.nix155
-rw-r--r--nixpkgs/nixos/tests/rspamd.nix313
-rw-r--r--nixpkgs/nixos/tests/rss2email.nix67
-rw-r--r--nixpkgs/nixos/tests/rstudio-server.nix24
-rw-r--r--nixpkgs/nixos/tests/rsyncd.nix36
-rw-r--r--nixpkgs/nixos/tests/rsyslogd.nix40
-rw-r--r--nixpkgs/nixos/tests/rxe.nix47
-rw-r--r--nixpkgs/nixos/tests/sabnzbd.nix25
-rw-r--r--nixpkgs/nixos/tests/samba-wsdd.nix42
-rw-r--r--nixpkgs/nixos/tests/samba.nix46
-rw-r--r--nixpkgs/nixos/tests/sane.nix85
-rw-r--r--nixpkgs/nixos/tests/sanoid.nix130
-rw-r--r--nixpkgs/nixos/tests/scaphandre.nix18
-rw-r--r--nixpkgs/nixos/tests/schleuder.nix126
-rw-r--r--nixpkgs/nixos/tests/scrutiny.nix70
-rw-r--r--nixpkgs/nixos/tests/sddm.nix67
-rw-r--r--nixpkgs/nixos/tests/seafile.nix115
-rw-r--r--nixpkgs/nixos/tests/searx.nix114
-rw-r--r--nixpkgs/nixos/tests/seatd.nix51
-rw-r--r--nixpkgs/nixos/tests/service-runner.nix36
-rw-r--r--nixpkgs/nixos/tests/sftpgo.nix382
-rw-r--r--nixpkgs/nixos/tests/sfxr-qt.nix32
-rw-r--r--nixpkgs/nixos/tests/sgt-puzzles.nix34
-rw-r--r--nixpkgs/nixos/tests/shadow.nix172
-rw-r--r--nixpkgs/nixos/tests/shadowsocks/common.nix85
-rw-r--r--nixpkgs/nixos/tests/shadowsocks/default.nix16
-rw-r--r--nixpkgs/nixos/tests/shattered-pixel-dungeon.nix28
-rw-r--r--nixpkgs/nixos/tests/shiori.nix80
-rw-r--r--nixpkgs/nixos/tests/signal-desktop.nix69
-rw-r--r--nixpkgs/nixos/tests/simple.nix17
-rw-r--r--nixpkgs/nixos/tests/sing-box.nix48
-rw-r--r--nixpkgs/nixos/tests/slimserver.nix47
-rw-r--r--nixpkgs/nixos/tests/slurm.nix168
-rw-r--r--nixpkgs/nixos/tests/smokeping.nix36
-rw-r--r--nixpkgs/nixos/tests/snapcast.nix90
-rw-r--r--nixpkgs/nixos/tests/snapper.nix35
-rw-r--r--nixpkgs/nixos/tests/snmpd.nix23
-rw-r--r--nixpkgs/nixos/tests/soapui.nix24
-rw-r--r--nixpkgs/nixos/tests/soft-serve.nix102
-rw-r--r--nixpkgs/nixos/tests/sogo.nix58
-rw-r--r--nixpkgs/nixos/tests/solanum.nix97
-rw-r--r--nixpkgs/nixos/tests/sonarr.nix16
-rw-r--r--nixpkgs/nixos/tests/sonic-server.nix22
-rw-r--r--nixpkgs/nixos/tests/sourcehut/builds.nix54
-rw-r--r--nixpkgs/nixos/tests/sourcehut/default.nix6
-rw-r--r--nixpkgs/nixos/tests/sourcehut/git.nix96
-rw-r--r--nixpkgs/nixos/tests/sourcehut/nodes/common.nix107
-rw-r--r--nixpkgs/nixos/tests/spacecookie.nix56
-rw-r--r--nixpkgs/nixos/tests/spark/default.nix48
-rw-r--r--nixpkgs/nixos/tests/spark/spark_sample.py40
-rw-r--r--nixpkgs/nixos/tests/sqlite3-to-mysql.nix65
-rw-r--r--nixpkgs/nixos/tests/ssh-agent-auth.nix55
-rw-r--r--nixpkgs/nixos/tests/ssh-audit.nix104
-rw-r--r--nixpkgs/nixos/tests/ssh-keys.nix15
-rw-r--r--nixpkgs/nixos/tests/sslh.nix75
-rw-r--r--nixpkgs/nixos/tests/sssd-ldap.nix173
-rw-r--r--nixpkgs/nixos/tests/sssd.nix18
-rw-r--r--nixpkgs/nixos/tests/stalwart-mail.nix120
-rw-r--r--nixpkgs/nixos/tests/starship.nix42
-rw-r--r--nixpkgs/nixos/tests/step-ca.nix77
-rw-r--r--nixpkgs/nixos/tests/stratis/default.nix8
-rw-r--r--nixpkgs/nixos/tests/stratis/encryption.nix32
-rw-r--r--nixpkgs/nixos/tests/stratis/simple.nix39
-rw-r--r--nixpkgs/nixos/tests/strongswan-swanctl.nix148
-rw-r--r--nixpkgs/nixos/tests/stub-ld.nix73
-rw-r--r--nixpkgs/nixos/tests/stunnel.nix181
-rw-r--r--nixpkgs/nixos/tests/sudo-rs.nix95
-rw-r--r--nixpkgs/nixos/tests/sudo.nix103
-rw-r--r--nixpkgs/nixos/tests/suwayomi-server.nix46
-rw-r--r--nixpkgs/nixos/tests/swap-file-btrfs.nix50
-rw-r--r--nixpkgs/nixos/tests/swap-partition.nix48
-rw-r--r--nixpkgs/nixos/tests/swap-random-encryption.nix80
-rw-r--r--nixpkgs/nixos/tests/sway.nix193
-rw-r--r--nixpkgs/nixos/tests/switch-test.nix1409
-rw-r--r--nixpkgs/nixos/tests/sympa.nix35
-rw-r--r--nixpkgs/nixos/tests/syncthing-init.nix31
-rw-r--r--nixpkgs/nixos/tests/syncthing-many-devices.nix203
-rw-r--r--nixpkgs/nixos/tests/syncthing-no-settings.nix18
-rw-r--r--nixpkgs/nixos/tests/syncthing-relay.nix26
-rw-r--r--nixpkgs/nixos/tests/syncthing.nix65
-rw-r--r--nixpkgs/nixos/tests/sysinit-reactivation.nix107
-rw-r--r--nixpkgs/nixos/tests/systemd-analyze.nix46
-rw-r--r--nixpkgs/nixos/tests/systemd-binfmt.nix90
-rw-r--r--nixpkgs/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch25
-rw-r--r--nixpkgs/nixos/tests/systemd-boot.nix351
-rw-r--r--nixpkgs/nixos/tests/systemd-bpf.nix42
-rw-r--r--nixpkgs/nixos/tests/systemd-confinement.nix184
-rw-r--r--nixpkgs/nixos/tests/systemd-coredump.nix44
-rw-r--r--nixpkgs/nixos/tests/systemd-credentials-tpm2.nix69
-rw-r--r--nixpkgs/nixos/tests/systemd-cryptenroll.nix41
-rw-r--r--nixpkgs/nixos/tests/systemd-escaping.nix45
-rw-r--r--nixpkgs/nixos/tests/systemd-homed.nix99
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-bridge.nix63
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix47
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-fido2.nix48
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix56
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-password.nix56
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-tpm2.nix50
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-luks-unl0kr.nix75
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-modprobe.nix24
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-networkd-ssh.nix60
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-networkd.nix94
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-simple.nix48
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-swraid.nix66
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-vconsole.nix42
-rw-r--r--nixpkgs/nixos/tests/systemd-initrd-vlan.nix59
-rw-r--r--nixpkgs/nixos/tests/systemd-journal-gateway.nix90
-rw-r--r--nixpkgs/nixos/tests/systemd-journal-upload.nix101
-rw-r--r--nixpkgs/nixos/tests/systemd-journal.nix16
-rw-r--r--nixpkgs/nixos/tests/systemd-lock-handler.nix56
-rw-r--r--nixpkgs/nixos/tests/systemd-machinectl.nix114
-rw-r--r--nixpkgs/nixos/tests/systemd-misc.nix62
-rw-r--r--nixpkgs/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix81
-rw-r--r--nixpkgs/nixos/tests/systemd-networkd-dhcpserver.nix112
-rw-r--r--nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix331
-rw-r--r--nixpkgs/nixos/tests/systemd-networkd-vrf.nix182
-rw-r--r--nixpkgs/nixos/tests/systemd-networkd.nix123
-rw-r--r--nixpkgs/nixos/tests/systemd-no-tainted.nix14
-rw-r--r--nixpkgs/nixos/tests/systemd-nspawn-configfile.nix128
-rw-r--r--nixpkgs/nixos/tests/systemd-nspawn.nix52
-rw-r--r--nixpkgs/nixos/tests/systemd-oomd.nix54
-rw-r--r--nixpkgs/nixos/tests/systemd-portabled.nix51
-rw-r--r--nixpkgs/nixos/tests/systemd-repart.nix182
-rw-r--r--nixpkgs/nixos/tests/systemd-shutdown.nix27
-rw-r--r--nixpkgs/nixos/tests/systemd-sysupdate.nix66
-rw-r--r--nixpkgs/nixos/tests/systemd-sysusers-immutable.nix64
-rw-r--r--nixpkgs/nixos/tests/systemd-sysusers-mutable.nix71
-rw-r--r--nixpkgs/nixos/tests/systemd-timesyncd-nscd-dnssec.nix61
-rw-r--r--nixpkgs/nixos/tests/systemd-timesyncd.nix54
-rw-r--r--nixpkgs/nixos/tests/systemd-user-tmpfiles-rules.nix35
-rw-r--r--nixpkgs/nixos/tests/systemd-userdbd.nix32
-rw-r--r--nixpkgs/nixos/tests/systemd.nix205
-rw-r--r--nixpkgs/nixos/tests/systemtap.nix50
-rw-r--r--nixpkgs/nixos/tests/tandoor-recipes.nix41
-rw-r--r--nixpkgs/nixos/tests/tang.nix81
-rw-r--r--nixpkgs/nixos/tests/taskserver.nix275
-rw-r--r--nixpkgs/nixos/tests/tayga.nix237
-rw-r--r--nixpkgs/nixos/tests/teeworlds.nix55
-rw-r--r--nixpkgs/nixos/tests/telegraf.nix33
-rw-r--r--nixpkgs/nixos/tests/teleport.nix116
-rw-r--r--nixpkgs/nixos/tests/terminal-emulators.nix217
-rw-r--r--nixpkgs/nixos/tests/thelounge.nix31
-rw-r--r--nixpkgs/nixos/tests/tiddlywiki.nix69
-rw-r--r--nixpkgs/nixos/tests/tigervnc.nix53
-rw-r--r--nixpkgs/nixos/tests/timescaledb.nix93
-rw-r--r--nixpkgs/nixos/tests/timezone.nix50
-rw-r--r--nixpkgs/nixos/tests/tinc/default.nix139
-rw-r--r--nixpkgs/nixos/tests/tinc/snakeoil-keys.nix157
-rw-r--r--nixpkgs/nixos/tests/tinydns.nix40
-rw-r--r--nixpkgs/nixos/tests/tinyproxy.nix20
-rw-r--r--nixpkgs/nixos/tests/tinywl.nix59
-rw-r--r--nixpkgs/nixos/tests/tmate-ssh-server.nix74
-rw-r--r--nixpkgs/nixos/tests/tomcat.nix27
-rw-r--r--nixpkgs/nixos/tests/tor.nix23
-rw-r--r--nixpkgs/nixos/tests/tracee.nix68
-rw-r--r--nixpkgs/nixos/tests/traefik.nix98
-rw-r--r--nixpkgs/nixos/tests/trafficserver.nix179
-rw-r--r--nixpkgs/nixos/tests/transmission.nix24
-rw-r--r--nixpkgs/nixos/tests/trezord.nix19
-rw-r--r--nixpkgs/nixos/tests/trickster.nix37
-rw-r--r--nixpkgs/nixos/tests/trilium-server.nix53
-rw-r--r--nixpkgs/nixos/tests/tsja.nix32
-rw-r--r--nixpkgs/nixos/tests/tsm-client-gui.nix57
-rw-r--r--nixpkgs/nixos/tests/tuptime.nix29
-rw-r--r--nixpkgs/nixos/tests/turbovnc-headless-server.nix172
-rw-r--r--nixpkgs/nixos/tests/tuxguitar.nix24
-rw-r--r--nixpkgs/nixos/tests/twingate.nix14
-rw-r--r--nixpkgs/nixos/tests/txredisapi.nix29
-rw-r--r--nixpkgs/nixos/tests/typesense.nix24
-rw-r--r--nixpkgs/nixos/tests/ucarp.nix66
-rw-r--r--nixpkgs/nixos/tests/udisks2.nix72
-rw-r--r--nixpkgs/nixos/tests/ulogd/ulogd.nix56
-rw-r--r--nixpkgs/nixos/tests/ulogd/ulogd.py49
-rw-r--r--nixpkgs/nixos/tests/unbound.nix315
-rw-r--r--nixpkgs/nixos/tests/unifi.nix38
-rw-r--r--nixpkgs/nixos/tests/upnp.nix99
-rw-r--r--nixpkgs/nixos/tests/uptermd.nix66
-rw-r--r--nixpkgs/nixos/tests/uptime-kuma.nix17
-rw-r--r--nixpkgs/nixos/tests/urn-timer.nix26
-rw-r--r--nixpkgs/nixos/tests/usbguard.nix62
-rw-r--r--nixpkgs/nixos/tests/user-activation-scripts.nix36
-rw-r--r--nixpkgs/nixos/tests/user-expiry.nix70
-rw-r--r--nixpkgs/nixos/tests/user-home-mode.nix27
-rw-r--r--nixpkgs/nixos/tests/uwsgi.nix81
-rw-r--r--nixpkgs/nixos/tests/v2ray.nix91
-rw-r--r--nixpkgs/nixos/tests/varnish.nix55
-rw-r--r--nixpkgs/nixos/tests/vault-agent.nix52
-rw-r--r--nixpkgs/nixos/tests/vault-dev.nix35
-rw-r--r--nixpkgs/nixos/tests/vault-postgresql.nix69
-rw-r--r--nixpkgs/nixos/tests/vault.nix25
-rw-r--r--nixpkgs/nixos/tests/vaultwarden.nix198
-rw-r--r--nixpkgs/nixos/tests/vector.nix37
-rw-r--r--nixpkgs/nixos/tests/vengi-tools.nix27
-rw-r--r--nixpkgs/nixos/tests/victoriametrics.nix33
-rw-r--r--nixpkgs/nixos/tests/vikunja.nix64
-rw-r--r--nixpkgs/nixos/tests/virtualbox.nix522
-rw-r--r--nixpkgs/nixos/tests/vscode-remote-ssh.nix124
-rw-r--r--nixpkgs/nixos/tests/vscodium.nix79
-rw-r--r--nixpkgs/nixos/tests/vsftpd.nix42
-rw-r--r--nixpkgs/nixos/tests/warzone2100.nix26
-rw-r--r--nixpkgs/nixos/tests/wasabibackend.nix38
-rw-r--r--nixpkgs/nixos/tests/watchdogd.nix22
-rw-r--r--nixpkgs/nixos/tests/web-apps/gotosocial.nix28
-rw-r--r--nixpkgs/nixos/tests/web-apps/healthchecks.nix42
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon/default.nix9
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon/remote-databases.nix190
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon/script.nix52
-rw-r--r--nixpkgs/nixos/tests/web-apps/mastodon/standard.nix91
-rw-r--r--nixpkgs/nixos/tests/web-apps/monica.nix33
-rw-r--r--nixpkgs/nixos/tests/web-apps/netbox-upgrade.nix87
-rw-r--r--nixpkgs/nixos/tests/web-apps/netbox.nix318
-rw-r--r--nixpkgs/nixos/tests/web-apps/nifi.nix30
-rw-r--r--nixpkgs/nixos/tests/web-apps/peering-manager.nix40
-rw-r--r--nixpkgs/nixos/tests/web-apps/peertube.nix139
-rw-r--r--nixpkgs/nixos/tests/web-apps/phylactery.nix20
-rw-r--r--nixpkgs/nixos/tests/web-apps/pixelfed/default.nix8
-rw-r--r--nixpkgs/nixos/tests/web-apps/pixelfed/standard.nix38
-rw-r--r--nixpkgs/nixos/tests/web-apps/pretalx.nix31
-rw-r--r--nixpkgs/nixos/tests/web-apps/snipe-it.nix101
-rw-r--r--nixpkgs/nixos/tests/web-apps/writefreely.nix44
-rw-r--r--nixpkgs/nixos/tests/web-servers/agate.nix27
-rw-r--r--nixpkgs/nixos/tests/web-servers/stargazer.nix127
-rw-r--r--nixpkgs/nixos/tests/web-servers/static-web-server.nix32
-rw-r--r--nixpkgs/nixos/tests/web-servers/ttyd.nix29
-rw-r--r--nixpkgs/nixos/tests/web-servers/unit-php.nix52
-rw-r--r--nixpkgs/nixos/tests/webhook.nix65
-rw-r--r--nixpkgs/nixos/tests/wiki-js.nix153
-rw-r--r--nixpkgs/nixos/tests/wine.nix51
-rw-r--r--nixpkgs/nixos/tests/wireguard/basic.nix73
-rw-r--r--nixpkgs/nixos/tests/wireguard/default.nix28
-rw-r--r--nixpkgs/nixos/tests/wireguard/generated.nix63
-rw-r--r--nixpkgs/nixos/tests/wireguard/make-peer.nix23
-rw-r--r--nixpkgs/nixos/tests/wireguard/namespaces.nix83
-rw-r--r--nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix12
-rw-r--r--nixpkgs/nixos/tests/wireguard/wg-quick.nix80
-rw-r--r--nixpkgs/nixos/tests/without-nix.nix32
-rw-r--r--nixpkgs/nixos/tests/wmderland.nix54
-rw-r--r--nixpkgs/nixos/tests/wordpress.nix101
-rw-r--r--nixpkgs/nixos/tests/wpa_supplicant.nix210
-rw-r--r--nixpkgs/nixos/tests/wrappers.nix112
-rw-r--r--nixpkgs/nixos/tests/xandikos.nix70
-rw-r--r--nixpkgs/nixos/tests/xautolock.nix22
-rw-r--r--nixpkgs/nixos/tests/xfce.nix75
-rw-r--r--nixpkgs/nixos/tests/xmonad-xdg-autostart.nix35
-rw-r--r--nixpkgs/nixos/tests/xmonad.nix117
-rw-r--r--nixpkgs/nixos/tests/xmpp/ejabberd.nix278
-rw-r--r--nixpkgs/nixos/tests/xmpp/prosody-mysql.nix124
-rw-r--r--nixpkgs/nixos/tests/xmpp/prosody.nix93
-rw-r--r--nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix90
-rw-r--r--nixpkgs/nixos/tests/xpadneo.nix18
-rw-r--r--nixpkgs/nixos/tests/xrdp-with-audio-pulseaudio.nix97
-rw-r--r--nixpkgs/nixos/tests/xrdp.nix47
-rw-r--r--nixpkgs/nixos/tests/xscreensaver.nix64
-rw-r--r--nixpkgs/nixos/tests/xss-lock.nix40
-rw-r--r--nixpkgs/nixos/tests/xterm.nix23
-rw-r--r--nixpkgs/nixos/tests/xxh.nix67
-rw-r--r--nixpkgs/nixos/tests/yabar.nix27
-rw-r--r--nixpkgs/nixos/tests/yggdrasil.nix172
-rw-r--r--nixpkgs/nixos/tests/zammad.nix65
-rw-r--r--nixpkgs/nixos/tests/zeronet-conservancy.nix25
-rw-r--r--nixpkgs/nixos/tests/zfs.nix268
-rw-r--r--nixpkgs/nixos/tests/zigbee2mqtt.nix32
-rw-r--r--nixpkgs/nixos/tests/zoneminder.nix23
-rw-r--r--nixpkgs/nixos/tests/zookeeper.nix46
-rw-r--r--nixpkgs/nixos/tests/zram-generator.nix42
-rw-r--r--nixpkgs/nixos/tests/zrepl.nix69
-rw-r--r--nixpkgs/nixos/tests/zsh-history.nix35
-rw-r--r--nixpkgs/nixos/tests/zwave-js.nix31
1007 files changed, 77475 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/3proxy.nix b/nixpkgs/nixos/tests/3proxy.nix
new file mode 100644
index 000000000000..b80b4e166d48
--- /dev/null
+++ b/nixpkgs/nixos/tests/3proxy.nix
@@ -0,0 +1,188 @@
+{ lib, pkgs, ... }: {
+  name = "3proxy";
+  meta.maintainers = with lib.maintainers; [ misuzu ];
+
+  nodes = {
+    peer0 = { lib, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.1";
+            prefixLength = 24;
+          }
+          {
+            address = "216.58.211.111";
+            prefixLength = 24;
+          }
+        ];
+      };
+    };
+
+    peer1 = { lib, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.2";
+            prefixLength = 24;
+          }
+          {
+            address = "216.58.211.112";
+            prefixLength = 24;
+          }
+        ];
+      };
+      # test that binding to [::] is working when ipv6 is disabled
+      networking.enableIPv6 = false;
+      services._3proxy = {
+        enable = true;
+        services = [
+          {
+            type = "admin";
+            bindPort = 9999;
+            auth = [ "none" ];
+          }
+          {
+            type = "proxy";
+            bindPort = 3128;
+            auth = [ "none" ];
+          }
+        ];
+      };
+      networking.firewall.allowedTCPPorts = [ 3128 9999 ];
+    };
+
+    peer2 = { lib, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.3";
+            prefixLength = 24;
+          }
+          {
+            address = "216.58.211.113";
+            prefixLength = 24;
+          }
+        ];
+      };
+      services._3proxy = {
+        enable = true;
+        services = [
+          {
+            type = "admin";
+            bindPort = 9999;
+            auth = [ "none" ];
+          }
+          {
+            type = "proxy";
+            bindPort = 3128;
+            auth = [ "iponly" ];
+            acl = [
+              {
+                rule = "allow";
+              }
+            ];
+          }
+        ];
+      };
+      networking.firewall.allowedTCPPorts = [ 3128 9999 ];
+    };
+
+    peer3 = { lib, pkgs, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.4";
+            prefixLength = 24;
+          }
+          {
+            address = "216.58.211.114";
+            prefixLength = 24;
+          }
+        ];
+      };
+      services._3proxy = {
+        enable = true;
+        usersFile = pkgs.writeText "3proxy.passwd" ''
+          admin:CR:$1$.GUV4Wvk$WnEVQtaqutD9.beO5ar1W/
+        '';
+        services = [
+          {
+            type = "admin";
+            bindPort = 9999;
+            auth = [ "none" ];
+          }
+          {
+            type = "proxy";
+            bindPort = 3128;
+            auth = [ "strong" ];
+            acl = [
+              {
+                rule = "allow";
+              }
+            ];
+          }
+        ];
+      };
+      networking.firewall.allowedTCPPorts = [ 3128 9999 ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    peer0.systemctl("start network-online.target")
+    peer0.wait_for_unit("network-online.target")
+
+    peer1.wait_for_unit("3proxy.service")
+    peer1.wait_for_open_port(9999)
+
+    # test none auth
+    peer0.succeed(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://216.58.211.112:9999"
+    )
+    peer0.succeed(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://192.168.0.2:9999"
+    )
+    peer0.succeed(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.2:3128 -S -O /dev/null http://127.0.0.1:9999"
+    )
+
+    peer2.wait_for_unit("3proxy.service")
+    peer2.wait_for_open_port(9999)
+
+    # test iponly auth
+    peer0.succeed(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://216.58.211.113:9999"
+    )
+    peer0.fail(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://192.168.0.3:9999"
+    )
+    peer0.fail(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.3:3128 -S -O /dev/null http://127.0.0.1:9999"
+    )
+
+    peer3.wait_for_unit("3proxy.service")
+    peer3.wait_for_open_port(9999)
+
+    # test strong auth
+    peer0.succeed(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999"
+    )
+    peer0.fail(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://admin:bigsecret\@192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999"
+    )
+    peer0.fail(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://216.58.211.114:9999"
+    )
+    peer0.fail(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://192.168.0.4:9999"
+    )
+    peer0.fail(
+        "${pkgs.wget}/bin/wget -e use_proxy=yes -e http_proxy=http://192.168.0.4:3128 -S -O /dev/null http://127.0.0.1:9999"
+    )
+  '';
+}
diff --git a/nixpkgs/nixos/tests/aaaaxy.nix b/nixpkgs/nixos/tests/aaaaxy.nix
new file mode 100644
index 000000000000..19861198c369
--- /dev/null
+++ b/nixpkgs/nixos/tests/aaaaxy.nix
@@ -0,0 +1,29 @@
+{ pkgs, lib, ... }: {
+  name = "aaaaxy";
+  meta.maintainers = with lib.maintainers; [ Luflosi ];
+
+  nodes.machine = {
+    imports = [
+      ./common/x11.nix
+    ];
+  };
+
+  # This starts the game from a known state, feeds it a prerecorded set of button presses
+  # and then checks if the final game state is identical to the expected state.
+  # This is also what AAAAXY's CI system does and serves as a good sanity check.
+  testScript = ''
+    machine.wait_for_x()
+
+    machine.succeed(
+      # benchmark.dem needs to be in a mutable directory,
+      # so we can't just refer to the file in the Nix store directly
+      "mkdir -p '/tmp/aaaaxy/assets/demos/'",
+      "ln -s '${pkgs.aaaaxy.testing_infra}/assets/demos/benchmark.dem' '/tmp/aaaaxy/assets/demos/'",
+      """
+        '${pkgs.aaaaxy.testing_infra}/scripts/regression-test-demo.sh' \
+        'aaaaxy' 'on track for Any%, All Paths, No Teleports and No Coil' \
+        '${pkgs.aaaaxy}/bin/aaaaxy' '/tmp/aaaaxy/assets/demos/benchmark.dem'
+      """,
+    )
+  '';
+}
diff --git a/nixpkgs/nixos/tests/acme-dns.nix b/nixpkgs/nixos/tests/acme-dns.nix
new file mode 100644
index 000000000000..92d9498fe714
--- /dev/null
+++ b/nixpkgs/nixos/tests/acme-dns.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "acme-dns";
+
+  nodes.machine = { pkgs, ... }: {
+    services.acme-dns = {
+      enable = true;
+      settings = {
+        general = rec {
+          domain = "acme-dns.home.arpa";
+          nsname = domain;
+          nsadmin = "admin.home.arpa";
+          records = [
+            "${domain}. A 127.0.0.1"
+            "${domain}. AAAA ::1"
+            "${domain}. NS ${domain}."
+          ];
+        };
+        logconfig.loglevel = "debug";
+      };
+    };
+    environment.systemPackages = with pkgs; [ curl bind ];
+  };
+
+  testScript = ''
+    import json
+
+    machine.wait_for_unit("acme-dns.service")
+    machine.wait_for_open_port(53) # dns
+    machine.wait_for_open_port(8080) # http api
+
+    result = machine.succeed("curl --fail -X POST http://localhost:8080/register")
+    print(result)
+
+    registration = json.loads(result)
+
+    machine.succeed(f'dig -t TXT @localhost {registration["fulldomain"]} | grep "SOA" | grep "admin.home.arpa"')
+
+    # acme-dns exspects a TXT value string length of exactly 43 chars
+    txt = "___dummy_validation_token_for_txt_record___"
+
+    machine.succeed(
+      "curl --fail -X POST http://localhost:8080/update "
+      + f' -H "X-Api-User: {registration["username"]}"'
+      + f' -H "X-Api-Key: {registration["password"]}"'
+      + f' -d \'{{"subdomain":"{registration["subdomain"]}", "txt":"{txt}"}}\'''
+    )
+
+    assert txt in machine.succeed(f'dig -t TXT +short @localhost {registration["fulldomain"]}')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/acme.nix b/nixpkgs/nixos/tests/acme.nix
new file mode 100644
index 000000000000..272782dc2f62
--- /dev/null
+++ b/nixpkgs/nixos/tests/acme.nix
@@ -0,0 +1,716 @@
+{ pkgs, lib, ... }: let
+  commonConfig = ./common/acme/client;
+
+  dnsServerIP = nodes: nodes.dnsserver.networking.primaryIPAddress;
+
+  dnsScript = nodes: let
+    dnsAddress = dnsServerIP nodes;
+  in pkgs.writeShellScript "dns-hook.sh" ''
+    set -euo pipefail
+    echo '[INFO]' "[$2]" 'dns-hook.sh' $*
+    if [ "$1" = "present" ]; then
+      ${pkgs.curl}/bin/curl --data '{"host": "'"$2"'", "value": "'"$3"'"}' http://${dnsAddress}:8055/set-txt
+    else
+      ${pkgs.curl}/bin/curl --data '{"host": "'"$2"'"}' http://${dnsAddress}:8055/clear-txt
+    fi
+  '';
+
+  dnsConfig = nodes: {
+    dnsProvider = "exec";
+    dnsPropagationCheck = false;
+    environmentFile = pkgs.writeText "wildcard.env" ''
+      EXEC_PATH=${dnsScript nodes}
+      EXEC_POLLING_INTERVAL=1
+      EXEC_PROPAGATION_TIMEOUT=1
+      EXEC_SEQUENCE_INTERVAL=1
+    '';
+  };
+
+  documentRoot = pkgs.runCommand "docroot" {} ''
+    mkdir -p "$out"
+    echo hello world > "$out/index.html"
+  '';
+
+  vhostBase = {
+    forceSSL = true;
+    locations."/".root = documentRoot;
+  };
+
+  vhostBaseHttpd = {
+    forceSSL = true;
+    inherit documentRoot;
+  };
+
+  simpleConfig = {
+    security.acme = {
+      certs."http.example.test" = {
+        listenHTTP = ":80";
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
+  };
+
+  # Base specialisation config for testing general ACME features
+  webserverBasicConfig = {
+    services.nginx.enable = true;
+    services.nginx.virtualHosts."a.example.test" = vhostBase // {
+      enableACME = true;
+    };
+  };
+
+  # Generate specialisations for testing a web server
+  mkServerConfigs = { server, group, vhostBaseData, extraConfig ? {} }: let
+    baseConfig = { nodes, config, specialConfig ? {} }: lib.mkMerge [
+      {
+        security.acme = {
+          defaults = (dnsConfig nodes);
+          # One manual wildcard cert
+          certs."example.test" = {
+            domain = "*.example.test";
+          };
+        };
+
+        users.users."${config.services."${server}".user}".extraGroups = ["acme"];
+
+        services."${server}" = {
+          enable = true;
+          virtualHosts = {
+            # Run-of-the-mill vhost using HTTP-01 validation
+            "${server}-http.example.test" = vhostBaseData // {
+              serverAliases = [ "${server}-http-alias.example.test" ];
+              enableACME = true;
+            };
+
+            # Another which inherits the DNS-01 config
+            "${server}-dns.example.test" = vhostBaseData // {
+              serverAliases = [ "${server}-dns-alias.example.test" ];
+              enableACME = true;
+              # Set acmeRoot to null instead of using the default of "/var/lib/acme/acme-challenge"
+              # webroot + dnsProvider are mutually exclusive.
+              acmeRoot = null;
+            };
+
+            # One using the wildcard certificate
+            "${server}-wildcard.example.test" = vhostBaseData // {
+              serverAliases = [ "${server}-wildcard-alias.example.test" ];
+              useACMEHost = "example.test";
+            };
+          };
+        };
+
+        # Used to determine if service reload was triggered
+        systemd.targets."test-renew-${server}" = {
+          wants = [ "acme-${server}-http.example.test.service" ];
+          after = [ "acme-${server}-http.example.test.service" "${server}-config-reload.service" ];
+        };
+      }
+      specialConfig
+      extraConfig
+    ];
+  in {
+    "${server}".configuration = { nodes, config, ... }: baseConfig {
+      inherit nodes config;
+    };
+
+    # Test that server reloads when an alias is removed (and subsequently test removal works in acme)
+    "${server}-remove-alias".configuration = { nodes, config, ... }: baseConfig {
+      inherit nodes config;
+      specialConfig = {
+        # Remove an alias, but create a standalone vhost in its place for testing.
+        # This configuration results in certificate errors as useACMEHost does not imply
+        # append extraDomains, and thus we can validate the SAN is removed.
+        services."${server}" = {
+          virtualHosts."${server}-http.example.test".serverAliases = lib.mkForce [];
+          virtualHosts."${server}-http-alias.example.test" = vhostBaseData // {
+            useACMEHost = "${server}-http.example.test";
+          };
+        };
+      };
+    };
+
+    # Test that the server reloads when only the acme configuration is changed.
+    "${server}-change-acme-conf".configuration = { nodes, config, ... }: baseConfig {
+      inherit nodes config;
+      specialConfig = {
+        security.acme.certs."${server}-http.example.test" = {
+          keyType = "ec384";
+          # Also test that postRun is exec'd as root
+          postRun = "id | grep root";
+        };
+      };
+    };
+  };
+
+in {
+  name = "acme";
+  meta = {
+    maintainers = lib.teams.acme.members;
+    # Hard timeout in seconds. Average run time is about 7 minutes.
+    timeout = 1800;
+  };
+
+  nodes = {
+    # The fake ACME server which will respond to client requests
+    acme = { nodes, ... }: {
+      imports = [ ./common/acme/server ];
+      networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
+    };
+
+    # A fake DNS server which can be configured with records as desired
+    # Used to test DNS-01 challenge
+    dnsserver = { nodes, ... }: {
+      networking.firewall.allowedTCPPorts = [ 8055 53 ];
+      networking.firewall.allowedUDPPorts = [ 53 ];
+      systemd.services.pebble-challtestsrv = {
+        enable = true;
+        description = "Pebble ACME challenge test server";
+        wantedBy = [ "network.target" ];
+        serviceConfig = {
+          ExecStart = "${pkgs.pebble}/bin/pebble-challtestsrv -dns01 ':53' -defaultIPv6 '' -defaultIPv4 '${nodes.webserver.networking.primaryIPAddress}'";
+          # Required to bind on privileged ports.
+          AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        };
+      };
+    };
+
+    # A web server which will be the node requesting certs
+    webserver = { nodes, config, ... }: {
+      imports = [ commonConfig ];
+      networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      # OpenSSL will be used for more thorough certificate validation
+      environment.systemPackages = [ pkgs.openssl ];
+
+      # Set log level to info so that we can see when the service is reloaded
+      services.nginx.logError = "stderr info";
+
+      specialisation = {
+        # Tests HTTP-01 verification using Lego's built-in web server
+        http01lego.configuration = simpleConfig;
+
+        renew.configuration = lib.mkMerge [
+          simpleConfig
+          {
+            # Pebble provides 5 year long certs,
+            # needs to be higher than that to test renewal
+            security.acme.certs."http.example.test".validMinDays = 9999;
+          }
+        ];
+
+        # Tests that account creds can be safely changed.
+        accountchange.configuration = lib.mkMerge [
+          simpleConfig
+          {
+            security.acme.certs."http.example.test".email = "admin@example.test";
+          }
+        ];
+
+        # First derivation used to test general ACME features
+        general.configuration = { ... }: let
+          caDomain = nodes.acme.test-support.acme.caDomain;
+          email = config.security.acme.defaults.email;
+          # Exit 99 to make it easier to track if this is the reason a renew failed
+          accountCreateTester = ''
+            test -e accounts/${caDomain}/${email}/account.json || exit 99
+          '';
+        in lib.mkMerge [
+          webserverBasicConfig
+          {
+            # Used to test that account creation is collated into one service.
+            # These should not run until after acme-finished-a.example.test.target
+            systemd.services."b.example.test".preStart = accountCreateTester;
+            systemd.services."c.example.test".preStart = accountCreateTester;
+
+            services.nginx.virtualHosts."b.example.test" = vhostBase // {
+              enableACME = true;
+            };
+            services.nginx.virtualHosts."c.example.test" = vhostBase // {
+              enableACME = true;
+            };
+          }
+        ];
+
+        # Test OCSP Stapling
+        ocsp-stapling.configuration = { ... }: lib.mkMerge [
+          webserverBasicConfig
+          {
+            security.acme.certs."a.example.test".ocspMustStaple = true;
+            services.nginx.virtualHosts."a.example.test" = {
+              extraConfig = ''
+                ssl_stapling on;
+                ssl_stapling_verify on;
+              '';
+            };
+          }
+        ];
+
+        # Validate service relationships by adding a slow start service to nginx' wants.
+        # Reproducer for https://github.com/NixOS/nixpkgs/issues/81842
+        slow-startup.configuration = { ... }: lib.mkMerge [
+          webserverBasicConfig
+          {
+            systemd.services.my-slow-service = {
+              wantedBy = [ "multi-user.target" "nginx.service" ];
+              before = [ "nginx.service" ];
+              preStart = "sleep 5";
+              script = "${pkgs.python3}/bin/python -m http.server";
+            };
+
+            services.nginx.virtualHosts."slow.example.test" = {
+              forceSSL = true;
+              enableACME = true;
+              locations."/".proxyPass = "http://localhost:8000";
+            };
+          }
+        ];
+
+        concurrency-limit.configuration = {pkgs, ...}: lib.mkMerge [
+          webserverBasicConfig {
+            security.acme.maxConcurrentRenewals = 1;
+
+            services.nginx.virtualHosts = {
+              "f.example.test" = vhostBase // {
+                enableACME = true;
+              };
+              "g.example.test" = vhostBase // {
+                enableACME = true;
+              };
+              "h.example.test" = vhostBase // {
+                enableACME = true;
+              };
+            };
+
+            systemd.services = {
+              # check for mutual exclusion of starting renew services
+              "acme-f.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-f" ''
+                test "$(systemctl is-active acme-{g,h}.example.test.service | grep activating | wc -l)" -le 0
+                '');
+              "acme-g.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-g" ''
+                test "$(systemctl is-active acme-{f,h}.example.test.service | grep activating | wc -l)" -le 0
+                '');
+              "acme-h.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-h" ''
+                test "$(systemctl is-active acme-{g,f}.example.test.service | grep activating | wc -l)" -le 0
+                '');
+              };
+          }
+        ];
+
+        # Test lego internal server (listenHTTP option)
+        # Also tests useRoot option
+        lego-server.configuration = { ... }: {
+          security.acme.useRoot = true;
+          security.acme.certs."lego.example.test" = {
+            listenHTTP = ":80";
+            group = "nginx";
+          };
+          services.nginx.enable = true;
+          services.nginx.virtualHosts."lego.example.test" = {
+            useACMEHost = "lego.example.test";
+            onlySSL = true;
+          };
+        };
+
+      # Test compatibility with Caddy
+      # It only supports useACMEHost, hence not using mkServerConfigs
+      } // (let
+        baseCaddyConfig = { nodes, config, ... }: {
+          security.acme = {
+            defaults = (dnsConfig nodes);
+            # One manual wildcard cert
+            certs."example.test" = {
+              domain = "*.example.test";
+            };
+          };
+
+          users.users."${config.services.caddy.user}".extraGroups = ["acme"];
+
+          services.caddy = {
+            enable = true;
+            virtualHosts."a.example.test" = {
+              useACMEHost = "example.test";
+              extraConfig = ''
+                root * ${documentRoot}
+              '';
+            };
+          };
+        };
+      in {
+        caddy.configuration = baseCaddyConfig;
+
+        # Test that the server reloads when only the acme configuration is changed.
+        "caddy-change-acme-conf".configuration = { nodes, config, ... }: lib.mkMerge [
+          (baseCaddyConfig {
+            inherit nodes config;
+          })
+          {
+            security.acme.certs."example.test" = {
+              keyType = "ec384";
+            };
+          }
+        ];
+
+      # Test compatibility with Nginx
+      }) // (mkServerConfigs {
+          server = "nginx";
+          group = "nginx";
+          vhostBaseData = vhostBase;
+        })
+
+      # Test compatibility with Apache HTTPD
+        // (mkServerConfigs {
+          server = "httpd";
+          group = "wwwrun";
+          vhostBaseData = vhostBaseHttpd;
+          extraConfig = {
+            services.httpd.adminAddr = config.security.acme.defaults.email;
+          };
+        });
+    };
+
+    # The client will be used to curl the webserver to validate configuration
+    client = { nodes, ... }: {
+      imports = [ commonConfig ];
+      networking.nameservers = lib.mkForce [ (dnsServerIP nodes) ];
+
+      # OpenSSL will be used for more thorough certificate validation
+      environment.systemPackages = [ pkgs.openssl ];
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      caDomain = nodes.acme.test-support.acme.caDomain;
+      newServerSystem = nodes.webserver.config.system.build.toplevel;
+      switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
+    in
+    # Note, wait_for_unit does not work for oneshot services that do not have RemainAfterExit=true,
+    # this is because a oneshot goes from inactive => activating => inactive, and never
+    # reaches the active state. Targets do not have this issue.
+    ''
+      import time
+
+
+      TOTAL_RETRIES = 20
+
+
+      class BackoffTracker(object):
+          delay = 1
+          increment = 1
+
+          def handle_fail(self, retries, message) -> int:
+              assert retries < TOTAL_RETRIES, message
+
+              print(f"Retrying in {self.delay}s, {retries + 1}/{TOTAL_RETRIES}")
+              time.sleep(self.delay)
+
+              # Only increment after the first try
+              if retries == 0:
+                  self.delay += self.increment
+                  self.increment *= 2
+
+              return retries + 1
+
+
+      backoff = BackoffTracker()
+
+
+      def switch_to(node, name):
+          # On first switch, this will create a symlink to the current system so that we can
+          # quickly switch between derivations
+          root_specs = "/tmp/specialisation"
+          node.execute(
+            f"test -e {root_specs}"
+            f" || ln -s $(readlink /run/current-system)/specialisation {root_specs}"
+          )
+
+          switcher_path = f"/run/current-system/specialisation/{name}/bin/switch-to-configuration"
+          rc, _ = node.execute(f"test -e '{switcher_path}'")
+          if rc > 0:
+              switcher_path = f"/tmp/specialisation/{name}/bin/switch-to-configuration"
+
+          node.succeed(
+              f"{switcher_path} test"
+          )
+
+
+      # Ensures the issuer of our cert matches the chain
+      # and matches the issuer we expect it to be.
+      # It's a good validation to ensure the cert.pem and fullchain.pem
+      # are not still selfsigned after verification
+      def check_issuer(node, cert_name, issuer):
+          for fname in ("cert.pem", "fullchain.pem"):
+              actual_issuer = node.succeed(
+                  f"openssl x509 -noout -issuer -in /var/lib/acme/{cert_name}/{fname}"
+              ).partition("=")[2]
+              print(f"{fname} issuer: {actual_issuer}")
+              assert issuer.lower() in actual_issuer.lower()
+
+
+      # Ensure cert comes before chain in fullchain.pem
+      def check_fullchain(node, cert_name):
+          subject_data = node.succeed(
+              f"openssl crl2pkcs7 -nocrl -certfile /var/lib/acme/{cert_name}/fullchain.pem"
+              " | openssl pkcs7 -print_certs -noout"
+          )
+          for line in subject_data.lower().split("\n"):
+              if "subject" in line:
+                  print(f"First subject in fullchain.pem: {line}")
+                  assert cert_name.lower() in line
+                  return
+
+          assert False
+
+
+      def check_connection(node, domain, retries=0):
+          result = node.succeed(
+              "openssl s_client -brief -verify 2 -CAfile /tmp/ca.crt"
+              f" -servername {domain} -connect {domain}:443 < /dev/null 2>&1"
+          )
+
+          for line in result.lower().split("\n"):
+              if "verification" in line and "error" in line:
+                  retries = backoff.handle_fail(retries, f"Failed to connect to https://{domain}")
+                  return check_connection(node, domain, retries)
+
+
+      def check_connection_key_bits(node, domain, bits, retries=0):
+          result = node.succeed(
+              "openssl s_client -CAfile /tmp/ca.crt"
+              f" -servername {domain} -connect {domain}:443 < /dev/null"
+              " | openssl x509 -noout -text | grep -i Public-Key"
+          )
+          print("Key type:", result)
+
+          if bits not in result:
+              retries = backoff.handle_fail(retries, f"Did not find expected number of bits ({bits}) in key")
+              return check_connection_key_bits(node, domain, bits, retries)
+
+
+      def check_stapling(node, domain, retries=0):
+          # Pebble doesn't provide a full OCSP responder, so just check the URL
+          result = node.succeed(
+              "openssl s_client -CAfile /tmp/ca.crt"
+              f" -servername {domain} -connect {domain}:443 < /dev/null"
+              " | openssl x509 -noout -ocsp_uri"
+          )
+          print("OCSP Responder URL:", result)
+
+          if "${caDomain}:4002" not in result.lower():
+              retries = backoff.handle_fail(retries, "OCSP Stapling check failed")
+              return check_stapling(node, domain, retries)
+
+
+      def download_ca_certs(node, retries=0):
+          exit_code, _ = node.execute("curl https://${caDomain}:15000/roots/0 > /tmp/ca.crt")
+          exit_code_2, _ = node.execute(
+              "curl https://${caDomain}:15000/intermediate-keys/0 >> /tmp/ca.crt"
+          )
+
+          if exit_code + exit_code_2 > 0:
+              retries = backoff.handle_fail(retries, "Failed to connect to pebble to download root CA certs")
+              return download_ca_certs(node, retries)
+
+
+      start_all()
+
+      dnsserver.wait_for_unit("pebble-challtestsrv.service")
+      client.wait_for_unit("default.target")
+
+      client.succeed(
+          'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a'
+      )
+
+      acme.systemctl("start network-online.target")
+      acme.wait_for_unit("network-online.target")
+      acme.wait_for_unit("pebble.service")
+
+      download_ca_certs(client)
+
+      # Perform http-01 w/ lego test first
+      with subtest("Can request certificate with Lego's built in web server"):
+          switch_to(webserver, "http01lego")
+          webserver.wait_for_unit("acme-finished-http.example.test.target")
+          check_fullchain(webserver, "http.example.test")
+          check_issuer(webserver, "http.example.test", "pebble")
+
+      # Perform renewal test
+      with subtest("Can renew certificates when they expire"):
+          hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem")
+          switch_to(webserver, "renew")
+          webserver.wait_for_unit("acme-finished-http.example.test.target")
+          check_fullchain(webserver, "http.example.test")
+          check_issuer(webserver, "http.example.test", "pebble")
+          hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem")
+          assert hash != hash_after
+
+      # Perform account change test
+      with subtest("Handles email change correctly"):
+          hash = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem")
+          switch_to(webserver, "accountchange")
+          webserver.wait_for_unit("acme-finished-http.example.test.target")
+          check_fullchain(webserver, "http.example.test")
+          check_issuer(webserver, "http.example.test", "pebble")
+          hash_after = webserver.succeed("sha256sum /var/lib/acme/http.example.test/cert.pem")
+          # Has to do a full run to register account, which creates new certs.
+          assert hash != hash_after
+
+      # Perform general tests
+      switch_to(webserver, "general")
+
+      with subtest("Can request certificate with HTTP-01 challenge"):
+          webserver.wait_for_unit("acme-finished-a.example.test.target")
+          check_fullchain(webserver, "a.example.test")
+          check_issuer(webserver, "a.example.test", "pebble")
+          webserver.wait_for_unit("nginx.service")
+          check_connection(client, "a.example.test")
+
+      with subtest("Runs 1 cert for account creation before others"):
+          webserver.wait_for_unit("acme-finished-b.example.test.target")
+          webserver.wait_for_unit("acme-finished-c.example.test.target")
+          check_connection(client, "b.example.test")
+          check_connection(client, "c.example.test")
+
+      with subtest("Certificates and accounts have safe + valid permissions"):
+          # Nginx will set the group appropriately when enableACME is used
+          group = "nginx"
+          webserver.succeed(
+              f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
+          )
+          webserver.succeed(
+              f"test $(stat -L -c '%a %U %G' /var/lib/acme/.lego/a.example.test/**/a.example.test* | tee /dev/stderr | grep '600 acme {group}' | wc -l) -eq 4"
+          )
+          webserver.succeed(
+              f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test | tee /dev/stderr | grep '750 acme {group}' | wc -l) -eq 1"
+          )
+          webserver.succeed(
+              f"test $(find /var/lib/acme/accounts -type f -exec stat -L -c '%a %U %G' {{}} \\; | tee /dev/stderr | grep -v '600 acme {group}' | wc -l) -eq 0"
+          )
+
+      # Selfsigned certs tests happen late so we aren't fighting the system init triggering cert renewal
+      with subtest("Can generate valid selfsigned certs"):
+          webserver.succeed("systemctl clean acme-a.example.test.service --what=state")
+          webserver.succeed("systemctl start acme-selfsigned-a.example.test.service")
+          check_fullchain(webserver, "a.example.test")
+          check_issuer(webserver, "a.example.test", "minica")
+          # Check selfsigned permissions
+          webserver.succeed(
+              f"test $(stat -L -c '%a %U %G' /var/lib/acme/a.example.test/*.pem | tee /dev/stderr | grep '640 acme {group}' | wc -l) -eq 5"
+          )
+          # Will succeed if nginx can load the certs
+          webserver.succeed("systemctl start nginx-config-reload.service")
+
+      with subtest("Correctly implements OCSP stapling"):
+          switch_to(webserver, "ocsp-stapling")
+          webserver.wait_for_unit("acme-finished-a.example.test.target")
+          check_stapling(client, "a.example.test")
+
+      with subtest("Can request certificate with HTTP-01 using lego's internal web server"):
+          switch_to(webserver, "lego-server")
+          webserver.wait_for_unit("acme-finished-lego.example.test.target")
+          webserver.wait_for_unit("nginx.service")
+          webserver.succeed("echo HENLO && systemctl cat nginx.service")
+          webserver.succeed("test \"$(stat -c '%U' /var/lib/acme/* | uniq)\" = \"root\"")
+          check_connection(client, "a.example.test")
+          check_connection(client, "lego.example.test")
+
+      with subtest("Can request certificate with HTTP-01 when nginx startup is delayed"):
+          webserver.execute("systemctl stop nginx")
+          switch_to(webserver, "slow-startup")
+          webserver.wait_for_unit("acme-finished-slow.example.test.target")
+          check_issuer(webserver, "slow.example.test", "pebble")
+          webserver.wait_for_unit("nginx.service")
+          check_connection(client, "slow.example.test")
+
+      with subtest("Can limit concurrency of running renewals"):
+          switch_to(webserver, "concurrency-limit")
+          webserver.wait_for_unit("acme-finished-f.example.test.target")
+          webserver.wait_for_unit("acme-finished-g.example.test.target")
+          webserver.wait_for_unit("acme-finished-h.example.test.target")
+          check_connection(client, "f.example.test")
+          check_connection(client, "g.example.test")
+          check_connection(client, "h.example.test")
+
+      with subtest("Works with caddy"):
+          switch_to(webserver, "caddy")
+          webserver.wait_for_unit("acme-finished-example.test.target")
+          webserver.wait_for_unit("caddy.service")
+          # FIXME reloading caddy is not sufficient to load new certs.
+          # Restart it manually until this is fixed.
+          webserver.succeed("systemctl restart caddy.service")
+          check_connection(client, "a.example.test")
+
+      with subtest("security.acme changes reflect on caddy"):
+          switch_to(webserver, "caddy-change-acme-conf")
+          webserver.wait_for_unit("acme-finished-example.test.target")
+          webserver.wait_for_unit("caddy.service")
+          # FIXME reloading caddy is not sufficient to load new certs.
+          # Restart it manually until this is fixed.
+          webserver.succeed("systemctl restart caddy.service")
+          check_connection_key_bits(client, "a.example.test", "384")
+
+      domains = ["http", "dns", "wildcard"]
+      for server, logsrc in [
+          ("nginx", "journalctl -n 30 -u nginx.service"),
+          ("httpd", "tail -n 30 /var/log/httpd/*.log"),
+      ]:
+          wait_for_server = lambda: webserver.wait_for_unit(f"{server}.service")
+          with subtest(f"Works with {server}"):
+              try:
+                  switch_to(webserver, server)
+                  # Skip wildcard domain for this check ([:-1])
+                  for domain in domains[:-1]:
+                      webserver.wait_for_unit(
+                          f"acme-finished-{server}-{domain}.example.test.target"
+                      )
+              except Exception as err:
+                  _, output = webserver.execute(
+                      f"{logsrc} && ls -al /var/lib/acme/acme-challenge"
+                  )
+                  print(output)
+                  raise err
+
+              wait_for_server()
+
+              for domain in domains[:-1]:
+                  check_issuer(webserver, f"{server}-{domain}.example.test", "pebble")
+              for domain in domains:
+                  check_connection(client, f"{server}-{domain}.example.test")
+                  check_connection(client, f"{server}-{domain}-alias.example.test")
+
+          test_domain = f"{server}-{domains[0]}.example.test"
+
+          with subtest(f"Can reload {server} when timer triggers renewal"):
+              # Switch to selfsigned first
+              webserver.succeed(f"systemctl clean acme-{test_domain}.service --what=state")
+              webserver.succeed(f"systemctl start acme-selfsigned-{test_domain}.service")
+              check_issuer(webserver, test_domain, "minica")
+              webserver.succeed(f"systemctl start {server}-config-reload.service")
+              webserver.succeed(f"systemctl start test-renew-{server}.target")
+              check_issuer(webserver, test_domain, "pebble")
+              check_connection(client, test_domain)
+
+          with subtest("Can remove an alias from a domain + cert is updated"):
+              test_alias = f"{server}-{domains[0]}-alias.example.test"
+              switch_to(webserver, f"{server}-remove-alias")
+              webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
+              wait_for_server()
+              check_connection(client, test_domain)
+              rc, _s = client.execute(
+                  f"openssl s_client -CAfile /tmp/ca.crt -connect {test_alias}:443"
+                  " </dev/null 2>/dev/null | openssl x509 -noout -text"
+                  f" | grep DNS: | grep {test_alias}"
+              )
+              assert rc > 0, "Removed extraDomainName was not removed from the cert"
+
+          with subtest("security.acme changes reflect on web server"):
+              # Switch back to normal server config first, reset everything.
+              switch_to(webserver, server)
+              wait_for_server()
+              switch_to(webserver, f"{server}-change-acme-conf")
+              webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
+              wait_for_server()
+              check_connection_key_bits(client, test_domain, "384")
+    '';
+}
diff --git a/nixpkgs/nixos/tests/activation/etc-overlay-immutable.nix b/nixpkgs/nixos/tests/activation/etc-overlay-immutable.nix
new file mode 100644
index 000000000000..f347f9cf8efe
--- /dev/null
+++ b/nixpkgs/nixos/tests/activation/etc-overlay-immutable.nix
@@ -0,0 +1,36 @@
+{ lib, ... }: {
+
+  name = "activation-etc-overlay-immutable";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { pkgs, ... }: {
+    system.etc.overlay.enable = true;
+    system.etc.overlay.mutable = false;
+
+    # Prerequisites
+    systemd.sysusers.enable = true;
+    users.mutableUsers = false;
+    boot.initrd.systemd.enable = true;
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    specialisation.new-generation.configuration = {
+      environment.etc."newgen".text = "newgen";
+    };
+  };
+
+  testScript = ''
+    with subtest("/etc is mounted as an overlay"):
+      machine.succeed("findmnt --kernel --type overlay /etc")
+
+    with subtest("switching to the same generation"):
+      machine.succeed("/run/current-system/bin/switch-to-configuration test")
+
+    with subtest("switching to a new generation"):
+      machine.fail("stat /etc/newgen")
+
+      machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+      assert machine.succeed("cat /etc/newgen") == "newgen"
+  '';
+}
diff --git a/nixpkgs/nixos/tests/activation/etc-overlay-mutable.nix b/nixpkgs/nixos/tests/activation/etc-overlay-mutable.nix
new file mode 100644
index 000000000000..087c06408a71
--- /dev/null
+++ b/nixpkgs/nixos/tests/activation/etc-overlay-mutable.nix
@@ -0,0 +1,36 @@
+{ lib, ... }: {
+
+  name = "activation-etc-overlay-mutable";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { pkgs, ... }: {
+    system.etc.overlay.enable = true;
+    system.etc.overlay.mutable = true;
+
+    # Prerequisites
+    boot.initrd.systemd.enable = true;
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    specialisation.new-generation.configuration = {
+      environment.etc."newgen".text = "newgen";
+    };
+  };
+
+  testScript = ''
+    with subtest("/etc is mounted as an overlay"):
+      machine.succeed("findmnt --kernel --type overlay /etc")
+
+    with subtest("switching to the same generation"):
+      machine.succeed("/run/current-system/bin/switch-to-configuration test")
+
+    with subtest("switching to a new generation"):
+      machine.fail("stat /etc/newgen")
+      machine.succeed("echo -n 'mutable' > /etc/mutable")
+
+      machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+      assert machine.succeed("cat /etc/newgen") == "newgen"
+      assert machine.succeed("cat /etc/mutable") == "mutable"
+  '';
+}
diff --git a/nixpkgs/nixos/tests/activation/nix-channel.nix b/nixpkgs/nixos/tests/activation/nix-channel.nix
new file mode 100644
index 000000000000..d26ea98e56cc
--- /dev/null
+++ b/nixpkgs/nixos/tests/activation/nix-channel.nix
@@ -0,0 +1,26 @@
+{ lib, ... }:
+
+{
+
+  name = "activation-nix-channel";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = {
+    nix.channel.enable = true;
+  };
+
+  testScript = { nodes, ... }: ''
+    machine.start(allow_reboot=True)
+
+    assert machine.succeed("cat /root/.nix-channels") == "${nodes.machine.system.defaultChannel} nixos\n"
+
+    nixpkgs_unstable_channel = "https://nixos.org/channels/nixpkgs-unstable nixpkgs"
+    machine.succeed(f"echo '{nixpkgs_unstable_channel}' > /root/.nix-channels")
+
+    machine.reboot()
+
+    assert machine.succeed("cat /root/.nix-channels") == f"{nixpkgs_unstable_channel}\n"
+  '';
+
+}
diff --git a/nixpkgs/nixos/tests/activation/perlless.nix b/nixpkgs/nixos/tests/activation/perlless.nix
new file mode 100644
index 000000000000..4d784b4542f4
--- /dev/null
+++ b/nixpkgs/nixos/tests/activation/perlless.nix
@@ -0,0 +1,24 @@
+{ lib, ... }:
+
+{
+
+  name = "activation-perlless";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { pkgs, modulesPath, ... }: {
+    imports = [ "${modulesPath}/profiles/perlless.nix" ];
+
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    virtualisation.mountHostNixStore = false;
+    virtualisation.useNixStoreImage = true;
+  };
+
+  testScript = ''
+    perl_store_paths = machine.succeed("ls /nix/store | grep perl || true")
+    print(perl_store_paths)
+    assert len(perl_store_paths) == 0
+  '';
+
+}
diff --git a/nixpkgs/nixos/tests/activation/var.nix b/nixpkgs/nixos/tests/activation/var.nix
new file mode 100644
index 000000000000..1a546a7671c5
--- /dev/null
+++ b/nixpkgs/nixos/tests/activation/var.nix
@@ -0,0 +1,18 @@
+{ lib, ... }:
+
+{
+
+  name = "activation-var";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { };
+
+  testScript = ''
+    assert machine.succeed("stat -c '%a' /var/tmp") == "1777\n"
+    assert machine.succeed("stat -c '%a' /var/empty") == "555\n"
+    assert machine.succeed("stat -c '%U' /var/empty") == "root\n"
+    assert machine.succeed("stat -c '%G' /var/empty") == "root\n"
+    assert "i" in machine.succeed("lsattr -d /var/empty")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/adguardhome.nix b/nixpkgs/nixos/tests/adguardhome.nix
new file mode 100644
index 000000000000..80613ce82534
--- /dev/null
+++ b/nixpkgs/nixos/tests/adguardhome.nix
@@ -0,0 +1,136 @@
+{
+  name = "adguardhome";
+
+  nodes = {
+    nullConf = { ... }: { services.adguardhome = { enable = true; }; };
+
+    emptyConf = { lib, ... }: {
+      services.adguardhome = {
+        enable = true;
+      };
+    };
+
+    declarativeConf = { ... }: {
+      services.adguardhome = {
+        enable = true;
+
+        mutableSettings = false;
+        settings = {
+          schema_version = 0;
+          dns = {
+            bind_host = "0.0.0.0";
+            bootstrap_dns = "127.0.0.1";
+          };
+        };
+      };
+    };
+
+    mixedConf = { ... }: {
+      services.adguardhome = {
+        enable = true;
+
+        mutableSettings = true;
+        settings = {
+          schema_version = 0;
+          dns = {
+            bind_host = "0.0.0.0";
+            bootstrap_dns = "127.0.0.1";
+          };
+        };
+      };
+    };
+
+    dhcpConf = { lib, ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        # Configure static IP for DHCP server
+        useDHCP = false;
+        interfaces."eth1" = lib.mkForce {
+          useDHCP = false;
+          ipv4 = {
+            addresses = [{
+              address = "10.0.10.1";
+              prefixLength = 24;
+            }];
+
+            routes = [{
+              address = "10.0.10.0";
+              prefixLength = 24;
+            }];
+          };
+        };
+
+        # Required for DHCP
+        firewall.allowedUDPPorts = [ 67 68 ];
+      };
+
+      services.adguardhome = {
+        enable = true;
+        allowDHCP = true;
+        mutableSettings = false;
+        settings = {
+          schema_version = 0;
+          dns = {
+            bind_host = "0.0.0.0";
+            bootstrap_dns = "127.0.0.1";
+          };
+          dhcp = {
+            # This implicitly enables CAP_NET_RAW
+            enabled = true;
+            interface_name = "eth1";
+            local_domain_name = "lan";
+            dhcpv4 = {
+              gateway_ip = "10.0.10.1";
+              range_start = "10.0.10.100";
+              range_end = "10.0.10.101";
+              subnet_mask = "255.255.255.0";
+            };
+          };
+        };
+      };
+    };
+
+    client = { lib, ... }: {
+      virtualisation.vlans = [ 1 ];
+      networking = {
+        interfaces.eth1 = {
+          useDHCP = true;
+          ipv4.addresses = lib.mkForce [ ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    with subtest("Minimal (settings = null) config test"):
+        nullConf.wait_for_unit("adguardhome.service")
+
+    with subtest("Default config test"):
+        emptyConf.wait_for_unit("adguardhome.service")
+        emptyConf.wait_for_open_port(3000)
+
+    with subtest("Declarative config test, DNS will be reachable"):
+        declarativeConf.wait_for_unit("adguardhome.service")
+        declarativeConf.wait_for_open_port(53)
+        declarativeConf.wait_for_open_port(3000)
+
+    with subtest("Mixed config test, check whether merging works"):
+        mixedConf.wait_for_unit("adguardhome.service")
+        mixedConf.wait_for_open_port(53)
+        mixedConf.wait_for_open_port(3000)
+        # Test whether merging works properly, even if nothing is changed
+        mixedConf.systemctl("restart adguardhome.service")
+        mixedConf.wait_for_unit("adguardhome.service")
+        mixedConf.wait_for_open_port(3000)
+
+    with subtest("Testing successful DHCP start"):
+        dhcpConf.wait_for_unit("adguardhome.service")
+        client.systemctl("start network-online.target")
+        client.wait_for_unit("network-online.target")
+        # Test IP assignment via DHCP
+        dhcpConf.wait_until_succeeds("ping -c 5 10.0.10.100")
+        # Test hostname resolution over DHCP-provided DNS
+        dhcpConf.wait_until_succeeds("ping -c 5 client.lan")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/aesmd.nix b/nixpkgs/nixos/tests/aesmd.nix
new file mode 100644
index 000000000000..848e1c599201
--- /dev/null
+++ b/nixpkgs/nixos/tests/aesmd.nix
@@ -0,0 +1,102 @@
+{ pkgs, lib, ... }: {
+  name = "aesmd";
+  meta = {
+    maintainers = with lib.maintainers; [ trundle veehaitch ];
+  };
+
+  nodes.machine = { lib, ... }: {
+    services.aesmd = {
+      enable = true;
+      settings = {
+        defaultQuotingType = "ecdsa_256";
+        proxyType = "direct";
+        whitelistUrl = "http://nixos.org";
+      };
+    };
+
+    # Should have access to the AESM socket
+    users.users."sgxtest" = {
+      isNormalUser = true;
+      extraGroups = [ "sgx" ];
+    };
+
+    # Should NOT have access to the AESM socket
+    users.users."nosgxtest".isNormalUser = true;
+
+    # We don't have a real SGX machine in NixOS tests
+    systemd.services.aesmd.unitConfig.AssertPathExists = lib.mkForce [ ];
+
+    specialisation = {
+      withQuoteProvider.configuration = { ... }: {
+        services.aesmd = {
+          quoteProviderLibrary = pkgs.sgx-azure-dcap-client;
+          environment = {
+            AZDCAP_DEBUG_LOG_LEVEL = "INFO";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+    in
+    ''
+      def get_aesmd_pid():
+        status, main_pid = machine.systemctl("show --property MainPID --value aesmd.service")
+        assert status == 0, "Could not get MainPID of aesmd.service"
+        return main_pid.strip()
+
+      with subtest("aesmd.service starts"):
+        machine.wait_for_unit("aesmd.service")
+
+      main_pid = get_aesmd_pid()
+
+      with subtest("aesmd.service runtime directory permissions"):
+        runtime_dir = "/run/aesmd";
+        res = machine.succeed(f"stat -c '%a %U %G' {runtime_dir}").strip()
+        assert "750 aesmd sgx" == res, f"{runtime_dir} does not have the expected permissions: {res}"
+
+      with subtest("aesm.socket available on host"):
+        socket_path = "/var/run/aesmd/aesm.socket"
+        machine.wait_until_succeeds(f"test -S {socket_path}")
+        machine.succeed(f"test 777 -eq $(stat -c '%a' {socket_path})")
+        for op in [ "-r", "-w", "-x" ]:
+          machine.succeed(f"sudo -u sgxtest test {op} {socket_path}")
+          machine.fail(f"sudo -u nosgxtest test {op} {socket_path}")
+
+      with subtest("Copies white_list_cert_to_be_verify.bin"):
+        whitelist_path = "/var/opt/aesmd/data/white_list_cert_to_be_verify.bin"
+        whitelist_perms = machine.succeed(
+          f"nsenter -m -t {main_pid} ${pkgs.coreutils}/bin/stat -c '%a' {whitelist_path}"
+        ).strip()
+        assert "644" == whitelist_perms, f"white_list_cert_to_be_verify.bin has permissions {whitelist_perms}"
+
+      with subtest("Writes and binds aesm.conf in service namespace"):
+        aesmd_config = machine.succeed(f"nsenter -m -t {main_pid} ${pkgs.coreutils}/bin/cat /etc/aesmd.conf")
+
+        assert aesmd_config == "whitelist url = http://nixos.org\nproxy type = direct\ndefault quoting type = ecdsa_256\n", "aesmd.conf differs"
+
+      with subtest("aesmd.service without quote provider library has correct LD_LIBRARY_PATH"):
+        status, environment = machine.systemctl("show --property Environment --value aesmd.service")
+        assert status == 0, "Could not get Environment of aesmd.service"
+        env_by_name = dict(entry.split("=", 1) for entry in environment.split())
+        assert not env_by_name["LD_LIBRARY_PATH"], "LD_LIBRARY_PATH is not empty"
+
+      with subtest("aesmd.service with quote provider library starts"):
+        machine.succeed('${specialisations}/withQuoteProvider/bin/switch-to-configuration test')
+        machine.wait_for_unit("aesmd.service")
+
+      main_pid = get_aesmd_pid()
+
+      with subtest("aesmd.service with quote provider library has correct LD_LIBRARY_PATH"):
+        ld_library_path = machine.succeed(f"xargs -0 -L1 -a /proc/{main_pid}/environ | grep LD_LIBRARY_PATH")
+        assert ld_library_path.startswith("LD_LIBRARY_PATH=${pkgs.sgx-azure-dcap-client}/lib:"), \
+          "LD_LIBRARY_PATH is not set to the configured quote provider library"
+
+      with subtest("aesmd.service with quote provider library has set AZDCAP_DEBUG_LOG_LEVEL"):
+        azdcp_debug_log_level = machine.succeed(f"xargs -0 -L1 -a /proc/{main_pid}/environ | grep AZDCAP_DEBUG_LOG_LEVEL")
+        assert azdcp_debug_log_level == "AZDCAP_DEBUG_LOG_LEVEL=INFO\n", "AZDCAP_DEBUG_LOG_LEVEL is not set to INFO"
+    '';
+}
diff --git a/nixpkgs/nixos/tests/agda.nix b/nixpkgs/nixos/tests/agda.nix
new file mode 100644
index 000000000000..6f51300111ac
--- /dev/null
+++ b/nixpkgs/nixos/tests/agda.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  hello-world = pkgs.writeText "hello-world" ''
+    {-# OPTIONS --guardedness #-}
+    open import IO
+    open import Level
+
+    main = run {0â„“} (putStrLn "Hello World!")
+  '';
+in
+{
+  name = "agda";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ alexarice turion ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [
+      (pkgs.agda.withPackages {
+        pkgs = p: [ p.standard-library ];
+      })
+    ];
+    virtualisation.memorySize = 2000; # Agda uses a lot of memory
+  };
+
+  testScript = ''
+    assert (
+        "${pkgs.agdaPackages.lib.interfaceFile "Everything.agda"}" == "Everything.agdai"
+    ), "wrong interface file for Everything.agda"
+    assert (
+        "${pkgs.agdaPackages.lib.interfaceFile "tmp/Everything.agda.md"}" == "tmp/Everything.agdai"
+    ), "wrong interface file for tmp/Everything.agda.md"
+
+    # Minimal script that typechecks
+    machine.succeed("touch TestEmpty.agda")
+    machine.succeed("agda TestEmpty.agda")
+
+    # Hello world
+    machine.succeed(
+        "cp ${hello-world} HelloWorld.agda"
+    )
+    machine.succeed("agda -l standard-library -i . -c HelloWorld.agda")
+    # Check execution
+    assert "Hello World!" in machine.succeed(
+        "./HelloWorld"
+    ), "HelloWorld does not run properly"
+  '';
+}
+)
diff --git a/nixpkgs/nixos/tests/airsonic.nix b/nixpkgs/nixos/tests/airsonic.nix
new file mode 100644
index 000000000000..69f979726bce
--- /dev/null
+++ b/nixpkgs/nixos/tests/airsonic.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "airsonic";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ sumnerevans ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.airsonic = {
+        enable = true;
+        maxMemory = 800;
+      };
+    };
+
+  testScript = ''
+    def airsonic_is_up(_) -> bool:
+        status, _ = machine.execute("curl --fail http://localhost:4040/login")
+        return status == 0
+
+
+    machine.start()
+    machine.wait_for_unit("airsonic.service")
+    machine.wait_for_open_port(4040)
+
+    with machine.nested("Waiting for UI to work"):
+        retry(airsonic_is_up)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/akkoma.nix b/nixpkgs/nixos/tests/akkoma.nix
new file mode 100644
index 000000000000..287e2d485999
--- /dev/null
+++ b/nixpkgs/nixos/tests/akkoma.nix
@@ -0,0 +1,124 @@
+/*
+  End-to-end test for Akkoma.
+
+  Based in part on nixos/tests/pleroma.
+
+  TODO: Test federation.
+*/
+import ./make-test-python.nix ({ pkgs, package ? pkgs.akkoma, confined ? false, ... }:
+let
+  userPassword = "4LKOrGo8SgbPm1a6NclVU5Wb";
+
+  provisionUser = pkgs.writers.writeBashBin "provisionUser" ''
+    set -eu -o errtrace -o pipefail
+
+    pleroma_ctl user new jamy jamy@nixos.test --password '${userPassword}' --moderator --admin -y
+  '';
+
+  tlsCert = pkgs.runCommand "selfSignedCerts" {
+    nativeBuildInputs = with pkgs; [ openssl ];
+  } ''
+    mkdir -p $out
+    openssl req -x509 \
+      -subj '/CN=akkoma.nixos.test/' -days 49710 \
+      -addext 'subjectAltName = DNS:akkoma.nixos.test' \
+      -keyout "$out/key.pem" -newkey ed25519 \
+      -out "$out/cert.pem" -noenc
+  '';
+
+  sendToot = pkgs.writers.writeBashBin "sendToot" ''
+    set -eu -o errtrace -o pipefail
+
+    export REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt"
+
+    echo '${userPassword}' | ${pkgs.toot}/bin/toot login_cli -i "akkoma.nixos.test" -e "jamy@nixos.test"
+    echo "y" | ${pkgs.toot}/bin/toot post "hello world Jamy here"
+
+    # Retrieving timeline with toot currently broken due to incompatible timestamp format
+    # cf. <https://akkoma.dev/AkkomaGang/akkoma/issues/637> and <https://github.com/ihabunek/toot/issues/399>
+    #echo "y" | ${pkgs.toot}/bin/toot timeline | grep -F -q "hello world Jamy here"
+
+    # Test file upload
+    echo "y" | ${pkgs.toot}/bin/toot upload <(dd if=/dev/zero bs=1024 count=1024 status=none) \
+      | grep -F -q "https://akkoma.nixos.test/media"
+  '';
+
+  checkFe = pkgs.writers.writeBashBin "checkFe" ''
+    set -eu -o errtrace -o pipefail
+
+    paths=( / /static/{config,styles}.json /pleroma/admin/ )
+
+    for path in "''${paths[@]}"; do
+      diff \
+        <(${pkgs.curl}/bin/curl -f -S -s -o /dev/null -w '%{response_code}' "https://akkoma.nixos.test$path") \
+        <(echo -n 200)
+    done
+  '';
+
+  hosts = nodes: ''
+    ${nodes.akkoma.networking.primaryIPAddress} akkoma.nixos.test
+    ${nodes.client.networking.primaryIPAddress} client.nixos.test
+  '';
+in
+{
+  name = "akkoma";
+  nodes = {
+    client = { nodes, pkgs, config, ... }: {
+      security.pki.certificateFiles = [ "${tlsCert}/cert.pem" ];
+      networking.extraHosts = hosts nodes;
+    };
+
+    akkoma = { nodes, pkgs, config, ... }: {
+      networking.extraHosts = hosts nodes;
+      networking.firewall.allowedTCPPorts = [ 443 ];
+      environment.systemPackages = with pkgs; [ provisionUser ];
+      systemd.services.akkoma.confinement.enable = confined;
+
+      services.akkoma = {
+        enable = true;
+        package = package;
+        config = {
+          ":pleroma" = {
+            ":instance" = {
+              name = "NixOS test Akkoma server";
+              description = "NixOS test Akkoma server";
+              email = "akkoma@nixos.test";
+              notify_email = "akkoma@nixos.test";
+              registration_open = true;
+            };
+
+            ":media_proxy" = {
+              enabled = false;
+            };
+
+            "Pleroma.Web.Endpoint" = {
+              url.host = "akkoma.nixos.test";
+            };
+          };
+        };
+
+        nginx = {
+          addSSL = true;
+          sslCertificate = "${tlsCert}/cert.pem";
+          sslCertificateKey = "${tlsCert}/key.pem";
+        };
+      };
+
+      services.nginx.enable = true;
+      services.postgresql.enable = true;
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+    akkoma.wait_for_unit('akkoma-initdb.service')
+    akkoma.systemctl('restart akkoma-initdb.service')  # test repeated initialisation
+    akkoma.wait_for_unit('akkoma.service')
+    akkoma.wait_for_file('/run/akkoma/socket');
+    akkoma.succeed('${provisionUser}/bin/provisionUser')
+    akkoma.wait_for_unit('nginx.service')
+    client.succeed('${sendToot}/bin/sendToot')
+    client.succeed('${checkFe}/bin/checkFe')
+  '';
+})
+
diff --git a/nixpkgs/nixos/tests/alice-lg.nix b/nixpkgs/nixos/tests/alice-lg.nix
new file mode 100644
index 000000000000..640e60030a04
--- /dev/null
+++ b/nixpkgs/nixos/tests/alice-lg.nix
@@ -0,0 +1,44 @@
+# This test does a basic functionality check for alice-lg
+
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) optionalString;
+in
+makeTest {
+  name = "birdwatcher";
+  nodes = {
+    host1 = {
+      environment.systemPackages = with pkgs; [ jq ];
+      services.alice-lg = {
+        enable = true;
+        settings = {
+          server = {
+            listen_http = "[::]:7340";
+            enable_prefix_lookup = true;
+            asn = 1;
+            routes_store_refresh_parallelism = 5;
+            neighbors_store_refresh_parallelism = 10000;
+            routes_store_refresh_interval = 5;
+            neighbors_store_refresh_interval = 5;
+          };
+          housekeeping = {
+            interval = 5;
+            force_release_memory = true;
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    host1.wait_for_unit("alice-lg.service")
+    host1.wait_for_open_port(7340)
+    host1.succeed("curl http://[::]:7340 | grep 'Alice BGP Looking Glass'")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/all-terminfo.nix b/nixpkgs/nixos/tests/all-terminfo.nix
new file mode 100644
index 000000000000..2f5e56f09f26
--- /dev/null
+++ b/nixpkgs/nixos/tests/all-terminfo.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ... }: rec {
+  name = "all-terminfo";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ jkarlson ];
+  };
+
+  nodes.machine = { pkgs, config, lib, ... }:
+    let
+      infoFilter = name: drv:
+        let
+          o = builtins.tryEval drv;
+        in
+        o.success &&
+        lib.isDerivation o.value &&
+        o.value ? outputs &&
+        builtins.elem "terminfo" o.value.outputs &&
+        !o.value.meta.broken;
+      terminfos = lib.filterAttrs infoFilter pkgs;
+      excludedTerminfos = lib.filterAttrs (_: drv: !(builtins.elem drv.terminfo config.environment.systemPackages)) terminfos;
+      includedOuts = lib.filterAttrs (_: drv: builtins.elem drv.out config.environment.systemPackages) terminfos;
+    in
+    {
+      environment = {
+        enableAllTerminfo = true;
+        etc."terminfo-missing".text = builtins.concatStringsSep "\n" (builtins.attrNames excludedTerminfos);
+        etc."terminfo-extra-outs".text = builtins.concatStringsSep "\n" (builtins.attrNames includedOuts);
+      };
+    };
+
+  testScript =
+    ''
+      machine.fail("grep . /etc/terminfo-missing >&2")
+      machine.fail("grep . /etc/terminfo-extra-outs >&2")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/all-tests.nix b/nixpkgs/nixos/tests/all-tests.nix
new file mode 100644
index 000000000000..1144a5611dcf
--- /dev/null
+++ b/nixpkgs/nixos/tests/all-tests.nix
@@ -0,0 +1,999 @@
+{ system,
+  pkgs,
+
+  # Projects the test configuration into a the desired value; usually
+  # the test runner: `config: config.test`.
+  callTest,
+
+}:
+# The return value of this function will be an attrset with arbitrary depth and
+# the `anything` returned by callTest at its test leafs.
+# The tests not supported by `system` will be replaced with `{}`, so that
+# `passthru.tests` can contain links to those without breaking on architectures
+# where said tests are unsupported.
+# Example callTest that just extracts the derivation from the test:
+#   callTest = t: t.test;
+
+with pkgs.lib;
+
+let
+  discoverTests = val:
+    if isAttrs val
+    then
+      if hasAttr "test" val then callTest val
+      else mapAttrs (n: s: if n == "passthru" then s else discoverTests s) val
+    else if isFunction val
+      then
+        # Tests based on make-test-python.nix will return the second lambda
+        # in that file, which are then forwarded to the test definition
+        # following the `import make-test-python.nix` expression
+        # (if it is a function).
+        discoverTests (val { inherit system pkgs; })
+      else val;
+  handleTest = path: args:
+    discoverTests (import path ({ inherit system pkgs; } // args));
+  handleTestOn = systems: path: args:
+    if elem system systems then handleTest path args
+    else {};
+
+  nixosLib = import ../lib {
+    # Experimental features need testing too, but there's no point in warning
+    # about it, so we enable the feature flag.
+    featureFlags.minimalModules = {};
+  };
+  evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
+
+  inherit
+    (rec {
+      doRunTest = arg: ((import ../lib/testing-python.nix { inherit system pkgs; }).evalTest {
+        imports = [ arg readOnlyPkgs ];
+      }).config.result;
+      findTests = tree:
+        if tree?recurseForDerivations && tree.recurseForDerivations
+        then
+          mapAttrs
+            (k: findTests)
+            (builtins.removeAttrs tree ["recurseForDerivations"])
+        else callTest tree;
+
+      runTest = arg: let r = doRunTest arg; in findTests r;
+      runTestOn = systems: arg:
+        if elem system systems then runTest arg
+        else {};
+    })
+    runTest
+    runTestOn
+    ;
+
+  # Using a single instance of nixpkgs makes test evaluation faster.
+  # To make sure we don't accidentally depend on a modified pkgs, we make the
+  # related options read-only. We need to test the right configuration.
+  #
+  # If your service depends on a nixpkgs setting, first try to avoid that, but
+  # otherwise, you can remove the readOnlyPkgs import and test your service as
+  # usual.
+  readOnlyPkgs =
+    # TODO: We currently accept this for nixosTests, so that the `pkgs` argument
+    #       is consistent with `pkgs` in `pkgs.nixosTests`. Can we reinitialize
+    #       it with `allowAliases = false`?
+    # warnIf pkgs.config.allowAliases "nixosTests: pkgs includes aliases."
+    {
+      _class = "nixosTest";
+      node.pkgs = pkgs;
+    };
+
+in {
+
+  # Testing the test driver
+  nixos-test-driver = {
+    extra-python-packages = handleTest ./nixos-test-driver/extra-python-packages.nix {};
+    lib-extend = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./nixos-test-driver/lib-extend.nix {};
+    node-name = runTest ./nixos-test-driver/node-name.nix;
+    busybox = runTest ./nixos-test-driver/busybox.nix;
+    driver-timeout = pkgs.runCommand "ensure-timeout-induced-failure" {
+      failed = pkgs.testers.testBuildFailure ((runTest ./nixos-test-driver/timeout.nix).config.rawTestDerivation);
+    } ''
+      grep -F "timeout reached; test terminating" $failed/testBuildFailure.log
+      # The program will always be terminated by SIGTERM (143) if it waits for the deadline thread.
+      [[ 143 = $(cat $failed/testBuildFailure.exit) ]]
+      touch $out
+    '';
+  };
+
+  # NixOS vm tests and non-vm unit tests
+
+  _3proxy = runTest ./3proxy.nix;
+  aaaaxy = runTest ./aaaaxy.nix;
+  acme = runTest ./acme.nix;
+  acme-dns = handleTest ./acme-dns.nix {};
+  adguardhome = runTest ./adguardhome.nix;
+  aesmd = runTestOn ["x86_64-linux"] ./aesmd.nix;
+  agate = runTest ./web-servers/agate.nix;
+  agda = handleTest ./agda.nix {};
+  airsonic = handleTest ./airsonic.nix {};
+  akkoma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./akkoma.nix {};
+  akkoma-confined = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./akkoma.nix { confined = true; };
+  alice-lg = handleTest ./alice-lg.nix {};
+  allTerminfo = handleTest ./all-terminfo.nix {};
+  alps = handleTest ./alps.nix {};
+  amazon-init-shell = handleTest ./amazon-init-shell.nix {};
+  amazon-ssm-agent = handleTest ./amazon-ssm-agent.nix {};
+  amd-sev = runTest ./amd-sev.nix;
+  anbox = runTest ./anbox.nix;
+  angie-api = handleTest ./angie-api.nix {};
+  anki-sync-server = handleTest ./anki-sync-server.nix {};
+  anuko-time-tracker = handleTest ./anuko-time-tracker.nix {};
+  apcupsd = handleTest ./apcupsd.nix {};
+  apfs = runTest ./apfs.nix;
+  appliance-repart-image = runTest ./appliance-repart-image.nix;
+  apparmor = handleTest ./apparmor.nix {};
+  archi = handleTest ./archi.nix {};
+  atd = handleTest ./atd.nix {};
+  atop = handleTest ./atop.nix {};
+  atuin = handleTest ./atuin.nix {};
+  audiobookshelf = handleTest ./audiobookshelf.nix {};
+  auth-mysql = handleTest ./auth-mysql.nix {};
+  authelia = handleTest ./authelia.nix {};
+  avahi = handleTest ./avahi.nix {};
+  avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
+  ayatana-indicators = handleTest ./ayatana-indicators.nix {};
+  babeld = handleTest ./babeld.nix {};
+  bazarr = handleTest ./bazarr.nix {};
+  bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {};
+  beanstalkd = handleTest ./beanstalkd.nix {};
+  bees = handleTest ./bees.nix {};
+  binary-cache = handleTest ./binary-cache.nix {};
+  bind = handleTest ./bind.nix {};
+  bird = handleTest ./bird.nix {};
+  birdwatcher = handleTest ./birdwatcher.nix {};
+  bitcoind = handleTest ./bitcoind.nix {};
+  bittorrent = handleTest ./bittorrent.nix {};
+  blockbook-frontend = handleTest ./blockbook-frontend.nix {};
+  blocky = handleTest ./blocky.nix {};
+  boot = handleTestOn ["x86_64-linux" "aarch64-linux"] ./boot.nix {};
+  bootspec = handleTestOn ["x86_64-linux"] ./bootspec.nix {};
+  boot-stage1 = handleTest ./boot-stage1.nix {};
+  borgbackup = handleTest ./borgbackup.nix {};
+  botamusique = handleTest ./botamusique.nix {};
+  bpf = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bpf.nix {};
+  bpftune = handleTest ./bpftune.nix {};
+  breitbandmessung = handleTest ./breitbandmessung.nix {};
+  brscan5 = handleTest ./brscan5.nix {};
+  btrbk = handleTest ./btrbk.nix {};
+  btrbk-doas = handleTest ./btrbk-doas.nix {};
+  btrbk-no-timer = handleTest ./btrbk-no-timer.nix {};
+  btrbk-section-order = handleTest ./btrbk-section-order.nix {};
+  budgie = handleTest ./budgie.nix {};
+  buildbot = handleTest ./buildbot.nix {};
+  buildkite-agents = handleTest ./buildkite-agents.nix {};
+  c2fmzq = handleTest ./c2fmzq.nix {};
+  caddy = handleTest ./caddy.nix {};
+  cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {};
+  cage = handleTest ./cage.nix {};
+  cagebreak = handleTest ./cagebreak.nix {};
+  calibre-web = handleTest ./calibre-web.nix {};
+  calibre-server = handleTest ./calibre-server.nix {};
+  castopod = handleTest ./castopod.nix {};
+  cassandra_3_0 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_0; };
+  cassandra_3_11 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_11; };
+  cassandra_4 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_4; };
+  centrifugo = runTest ./centrifugo.nix;
+  ceph-multi-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-multi-node.nix {};
+  ceph-single-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node.nix {};
+  ceph-single-node-bluestore = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node-bluestore.nix {};
+  certmgr = handleTest ./certmgr.nix {};
+  cfssl = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cfssl.nix {};
+  cgit = handleTest ./cgit.nix {};
+  charliecloud = handleTest ./charliecloud.nix {};
+  chromium = (handleTestOn ["aarch64-linux" "x86_64-linux"] ./chromium.nix {}).stable or {};
+  chrony = handleTestOn ["aarch64-linux" "x86_64-linux"] ./chrony.nix {};
+  chrony-ptp = handleTestOn ["aarch64-linux" "x86_64-linux"] ./chrony-ptp.nix {};
+  cinnamon = handleTest ./cinnamon.nix {};
+  cinnamon-wayland = handleTest ./cinnamon-wayland.nix {};
+  cjdns = handleTest ./cjdns.nix {};
+  clickhouse = handleTest ./clickhouse.nix {};
+  cloud-init = handleTest ./cloud-init.nix {};
+  cloud-init-hostname = handleTest ./cloud-init-hostname.nix {};
+  cloudlog = handleTest ./cloudlog.nix {};
+  cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {};
+  cockpit = handleTest ./cockpit.nix {};
+  cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {};
+  code-server = handleTest ./code-server.nix {};
+  coder = handleTest ./coder.nix {};
+  collectd = handleTest ./collectd.nix {};
+  connman = handleTest ./connman.nix {};
+  consul = handleTest ./consul.nix {};
+  consul-template = handleTest ./consul-template.nix {};
+  containers-bridge = handleTest ./containers-bridge.nix {};
+  containers-custom-pkgs.nix = handleTest ./containers-custom-pkgs.nix {};
+  containers-ephemeral = handleTest ./containers-ephemeral.nix {};
+  containers-extra_veth = handleTest ./containers-extra_veth.nix {};
+  containers-hosts = handleTest ./containers-hosts.nix {};
+  containers-imperative = handleTest ./containers-imperative.nix {};
+  containers-ip = handleTest ./containers-ip.nix {};
+  containers-macvlans = handleTest ./containers-macvlans.nix {};
+  containers-names = handleTest ./containers-names.nix {};
+  containers-nested = handleTest ./containers-nested.nix {};
+  containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {};
+  containers-portforward = handleTest ./containers-portforward.nix {};
+  containers-reloadable = handleTest ./containers-reloadable.nix {};
+  containers-restart_networking = handleTest ./containers-restart_networking.nix {};
+  containers-tmpfs = handleTest ./containers-tmpfs.nix {};
+  containers-unified-hierarchy = handleTest ./containers-unified-hierarchy.nix {};
+  convos = handleTest ./convos.nix {};
+  corerad = handleTest ./corerad.nix {};
+  coturn = handleTest ./coturn.nix {};
+  couchdb = handleTest ./couchdb.nix {};
+  cri-o = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cri-o.nix {};
+  cups-pdf = handleTest ./cups-pdf.nix {};
+  curl-impersonate = handleTest ./curl-impersonate.nix {};
+  custom-ca = handleTest ./custom-ca.nix {};
+  croc = handleTest ./croc.nix {};
+  darling = handleTest ./darling.nix {};
+  dae = handleTest ./dae.nix {};
+  dconf = handleTest ./dconf.nix {};
+  deconz = handleTest ./deconz.nix {};
+  deepin = handleTest ./deepin.nix {};
+  deluge = handleTest ./deluge.nix {};
+  dendrite = handleTest ./matrix/dendrite.nix {};
+  dex-oidc = handleTest ./dex-oidc.nix {};
+  dhparams = handleTest ./dhparams.nix {};
+  disable-installer-tools = handleTest ./disable-installer-tools.nix {};
+  discourse = handleTest ./discourse.nix {};
+  dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {};
+  dnscrypt-wrapper = runTestOn ["x86_64-linux"] ./dnscrypt-wrapper;
+  dnsdist = import ./dnsdist.nix { inherit pkgs runTest; };
+  doas = handleTest ./doas.nix {};
+  docker = handleTestOn ["aarch64-linux" "x86_64-linux"] ./docker.nix {};
+  docker-rootless = handleTestOn ["aarch64-linux" "x86_64-linux"] ./docker-rootless.nix {};
+  docker-registry = handleTest ./docker-registry.nix {};
+  docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {};
+  docker-tools-cross = handleTestOn ["x86_64-linux" "aarch64-linux"] ./docker-tools-cross.nix {};
+  docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {};
+  documize = handleTest ./documize.nix {};
+  documentation = pkgs.callPackage ../modules/misc/documentation/test.nix { inherit nixosLib; };
+  doh-proxy-rust = handleTest ./doh-proxy-rust.nix {};
+  dokuwiki = handleTest ./dokuwiki.nix {};
+  dolibarr = handleTest ./dolibarr.nix {};
+  domination = handleTest ./domination.nix {};
+  dovecot = handleTest ./dovecot.nix {};
+  drawterm = discoverTests (import ./drawterm.nix);
+  drbd = handleTest ./drbd.nix {};
+  dublin-traceroute = handleTest ./dublin-traceroute.nix {};
+  earlyoom = handleTestOn ["x86_64-linux"] ./earlyoom.nix {};
+  early-mount-options = handleTest ./early-mount-options.nix {};
+  ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
+  ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
+  ecryptfs = handleTest ./ecryptfs.nix {};
+  fscrypt = handleTest ./fscrypt.nix {};
+  fastnetmon-advanced = runTest ./fastnetmon-advanced.nix;
+  ejabberd = handleTest ./xmpp/ejabberd.nix {};
+  elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
+  emacs-daemon = handleTest ./emacs-daemon.nix {};
+  endlessh = handleTest ./endlessh.nix {};
+  endlessh-go = handleTest ./endlessh-go.nix {};
+  engelsystem = handleTest ./engelsystem.nix {};
+  enlightenment = handleTest ./enlightenment.nix {};
+  env = handleTest ./env.nix {};
+  envfs = handleTest ./envfs.nix {};
+  envoy = handleTest ./envoy.nix {};
+  ergo = handleTest ./ergo.nix {};
+  ergochat = handleTest ./ergochat.nix {};
+  eris-server = handleTest ./eris-server.nix {};
+  esphome = handleTest ./esphome.nix {};
+  etc = pkgs.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; };
+  activation = pkgs.callPackage ../modules/system/activation/test.nix { };
+  activation-var = runTest ./activation/var.nix;
+  activation-nix-channel = runTest ./activation/nix-channel.nix;
+  activation-etc-overlay-mutable = runTest ./activation/etc-overlay-mutable.nix;
+  activation-etc-overlay-immutable = runTest ./activation/etc-overlay-immutable.nix;
+  activation-perlless = runTest ./activation/perlless.nix;
+  etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
+  etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
+  etebase-server = handleTest ./etebase-server.nix {};
+  etesync-dav = handleTest ./etesync-dav.nix {};
+  evcc = handleTest ./evcc.nix {};
+  fail2ban = handleTest ./fail2ban.nix { };
+  fakeroute = handleTest ./fakeroute.nix {};
+  fancontrol = handleTest ./fancontrol.nix {};
+  fanout = handleTest ./fanout.nix {};
+  fcitx5 = handleTest ./fcitx5 {};
+  fenics = handleTest ./fenics.nix {};
+  ferm = handleTest ./ferm.nix {};
+  ferretdb = handleTest ./ferretdb.nix {};
+  filesystems-overlayfs = runTest ./filesystems-overlayfs.nix;
+  firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
+  firefox-beta = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-beta; };
+  firefox-devedition = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-devedition; };
+  firefox-esr    = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job
+  firefox-esr-115 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-115; };
+  firejail = handleTest ./firejail.nix {};
+  firewall = handleTest ./firewall.nix { nftables = false; };
+  firewall-nftables = handleTest ./firewall.nix { nftables = true; };
+  fish = handleTest ./fish.nix {};
+  flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
+  floorp = handleTest ./firefox.nix { firefoxPackage = pkgs.floorp; };
+  fluentd = handleTest ./fluentd.nix {};
+  fluidd = handleTest ./fluidd.nix {};
+  fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
+  forgejo = handleTest ./forgejo.nix { };
+  freenet = handleTest ./freenet.nix {};
+  freeswitch = handleTest ./freeswitch.nix {};
+  freetube = discoverTests (import ./freetube.nix);
+  freshrss-sqlite = handleTest ./freshrss-sqlite.nix {};
+  freshrss-pgsql = handleTest ./freshrss-pgsql.nix {};
+  freshrss-http-auth = handleTest ./freshrss-http-auth.nix {};
+  frigate = handleTest ./frigate.nix {};
+  frp = handleTest ./frp.nix {};
+  frr = handleTest ./frr.nix {};
+  fsck = handleTest ./fsck.nix {};
+  fsck-systemd-stage-1 = handleTest ./fsck.nix { systemdStage1 = true; };
+  ft2-clone = handleTest ./ft2-clone.nix {};
+  legit = handleTest ./legit.nix {};
+  mimir = handleTest ./mimir.nix {};
+  garage = handleTest ./garage {};
+  gemstash = handleTest ./gemstash.nix {};
+  geoserver = runTest ./geoserver.nix;
+  gerrit = handleTest ./gerrit.nix {};
+  geth = handleTest ./geth.nix {};
+  ghostunnel = handleTest ./ghostunnel.nix {};
+  gitdaemon = handleTest ./gitdaemon.nix {};
+  gitea = handleTest ./gitea.nix { giteaPackage = pkgs.gitea; };
+  github-runner = handleTest ./github-runner.nix {};
+  gitlab = runTest ./gitlab.nix;
+  gitolite = handleTest ./gitolite.nix {};
+  gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {};
+  glusterfs = handleTest ./glusterfs.nix {};
+  gnome = handleTest ./gnome.nix {};
+  gnome-extensions = handleTest ./gnome-extensions.nix {};
+  gnome-flashback = handleTest ./gnome-flashback.nix {};
+  gnome-xorg = handleTest ./gnome-xorg.nix {};
+  gns3-server = handleTest ./gns3-server.nix {};
+  gnupg = handleTest ./gnupg.nix {};
+  go-neb = handleTest ./go-neb.nix {};
+  gobgpd = handleTest ./gobgpd.nix {};
+  gocd-agent = handleTest ./gocd-agent.nix {};
+  gocd-server = handleTest ./gocd-server.nix {};
+  gollum = handleTest ./gollum.nix {};
+  gonic = handleTest ./gonic.nix {};
+  google-oslogin = handleTest ./google-oslogin {};
+  goss = handleTest ./goss.nix {};
+  gotify-server = handleTest ./gotify-server.nix {};
+  gotosocial = runTest ./web-apps/gotosocial.nix;
+  grafana = handleTest ./grafana {};
+  grafana-agent = handleTest ./grafana-agent.nix {};
+  graphite = handleTest ./graphite.nix {};
+  graylog = handleTest ./graylog.nix {};
+  grocy = handleTest ./grocy.nix {};
+  grow-partition = runTest ./grow-partition.nix;
+  grub = handleTest ./grub.nix {};
+  guacamole-server = handleTest ./guacamole-server.nix {};
+  guix = handleTest ./guix {};
+  gvisor = handleTest ./gvisor.nix {};
+  hadoop = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop; };
+  hadoop_3_2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop_3_2; };
+  hadoop2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop2; };
+  haka = handleTest ./haka.nix {};
+  haste-server = handleTest ./haste-server.nix {};
+  haproxy = handleTest ./haproxy.nix {};
+  hardened = handleTest ./hardened.nix {};
+  harmonia = runTest ./harmonia.nix;
+  headscale = handleTest ./headscale.nix {};
+  healthchecks = handleTest ./web-apps/healthchecks.nix {};
+  hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; };
+  hbase_2_4 = handleTest ./hbase.nix { package=pkgs.hbase_2_4; };
+  hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; };
+  hddfancontrol = handleTest ./hddfancontrol.nix {};
+  hedgedoc = handleTest ./hedgedoc.nix {};
+  herbstluftwm = handleTest ./herbstluftwm.nix {};
+  homepage-dashboard = handleTest ./homepage-dashboard.nix {};
+  honk = runTest ./honk.nix;
+  installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
+  invidious = handleTest ./invidious.nix {};
+  livebook-service = handleTest ./livebook-service.nix {};
+  pyload = handleTest ./pyload.nix {};
+  oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {};
+  odoo = handleTest ./odoo.nix {};
+  odoo15 = handleTest ./odoo.nix { package = pkgs.odoo15; };
+  # 9pnet_virtio used to mount /nix partition doesn't support
+  # hibernation. This test happens to work on x86_64-linux but
+  # not on other platforms.
+  hibernate = handleTestOn ["x86_64-linux"] ./hibernate.nix {};
+  hibernate-systemd-stage-1 = handleTestOn ["x86_64-linux"] ./hibernate.nix { systemdStage1 = true; };
+  hitch = handleTest ./hitch {};
+  hledger-web = handleTest ./hledger-web.nix {};
+  hocker-fetchdocker = handleTest ./hocker-fetchdocker {};
+  hockeypuck = handleTest ./hockeypuck.nix { };
+  home-assistant = handleTest ./home-assistant.nix {};
+  hostname = handleTest ./hostname.nix {};
+  hound = handleTest ./hound.nix {};
+  hub = handleTest ./git/hub.nix {};
+  hydra = handleTest ./hydra {};
+  i3wm = handleTest ./i3wm.nix {};
+  icingaweb2 = handleTest ./icingaweb2.nix {};
+  iftop = handleTest ./iftop.nix {};
+  incron = handleTest ./incron.nix {};
+  incus = pkgs.recurseIntoAttrs (handleTest ./incus { inherit handleTestOn; });
+  influxdb = handleTest ./influxdb.nix {};
+  influxdb2 = handleTest ./influxdb2.nix {};
+  initrd-network-openvpn = handleTestOn [ "x86_64-linux" "i686-linux" ] ./initrd-network-openvpn {};
+  initrd-network-ssh = handleTest ./initrd-network-ssh {};
+  initrd-luks-empty-passphrase = handleTest ./initrd-luks-empty-passphrase.nix {};
+  initrdNetwork = handleTest ./initrd-network.nix {};
+  initrd-secrets = handleTest ./initrd-secrets.nix {};
+  initrd-secrets-changing = handleTest ./initrd-secrets-changing.nix {};
+  input-remapper = handleTest ./input-remapper.nix {};
+  inspircd = handleTest ./inspircd.nix {};
+  installer = handleTest ./installer.nix {};
+  installer-systemd-stage-1 = handleTest ./installer-systemd-stage-1.nix {};
+  intune = handleTest ./intune.nix {};
+  invoiceplane = handleTest ./invoiceplane.nix {};
+  iodine = handleTest ./iodine.nix {};
+  ipv6 = handleTest ./ipv6.nix {};
+  iscsi-multipath-root = handleTest ./iscsi-multipath-root.nix {};
+  iscsi-root = handleTest ./iscsi-root.nix {};
+  isso = handleTest ./isso.nix {};
+  jackett = handleTest ./jackett.nix {};
+  jellyfin = handleTest ./jellyfin.nix {};
+  jenkins = handleTest ./jenkins.nix {};
+  jenkins-cli = handleTest ./jenkins-cli.nix {};
+  jibri = handleTest ./jibri.nix {};
+  jirafeau = handleTest ./jirafeau.nix {};
+  jitsi-meet = handleTest ./jitsi-meet.nix {};
+  jool = import ./jool.nix { inherit pkgs runTest; };
+  k3s = handleTest ./k3s {};
+  kafka = handleTest ./kafka.nix {};
+  kanidm = handleTest ./kanidm.nix {};
+  karma = handleTest ./karma.nix {};
+  kavita = handleTest ./kavita.nix {};
+  kbd-setfont-decompress = handleTest ./kbd-setfont-decompress.nix {};
+  kbd-update-search-paths-patch = handleTest ./kbd-update-search-paths-patch.nix {};
+  kea = handleTest ./kea.nix {};
+  keepalived = handleTest ./keepalived.nix {};
+  keepassxc = handleTest ./keepassxc.nix {};
+  kerberos = handleTest ./kerberos/default.nix {};
+  kernel-generic = handleTest ./kernel-generic.nix {};
+  kernel-latest-ath-user-regd = handleTest ./kernel-latest-ath-user-regd.nix {};
+  kernel-rust = handleTestOn ["x86_64-linux"] ./kernel-rust.nix {};
+  keter = handleTest ./keter.nix {};
+  kexec = handleTest ./kexec.nix {};
+  keycloak = discoverTests (import ./keycloak.nix);
+  keyd = handleTest ./keyd.nix {};
+  keymap = handleTest ./keymap.nix {};
+  knot = handleTest ./knot.nix {};
+  komga = handleTest ./komga.nix {};
+  krb5 = discoverTests (import ./krb5 {});
+  ksm = handleTest ./ksm.nix {};
+  kthxbye = handleTest ./kthxbye.nix {};
+  kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {};
+  kubo = import ./kubo { inherit recurseIntoAttrs runTest; };
+  ladybird = handleTest ./ladybird.nix {};
+  languagetool = handleTest ./languagetool.nix {};
+  lanraragi = handleTest ./lanraragi.nix {};
+  latestKernel.login = handleTest ./login.nix { latestKernel = true; };
+  leaps = handleTest ./leaps.nix {};
+  lemmy = handleTest ./lemmy.nix {};
+  libinput = handleTest ./libinput.nix {};
+  libreddit = handleTest ./libreddit.nix {};
+  librenms = handleTest ./librenms.nix {};
+  libresprite = handleTest ./libresprite.nix {};
+  libreswan = handleTest ./libreswan.nix {};
+  librewolf = handleTest ./firefox.nix { firefoxPackage = pkgs.librewolf; };
+  libuiohook = handleTest ./libuiohook.nix {};
+  libvirtd = handleTest ./libvirtd.nix {};
+  lidarr = handleTest ./lidarr.nix {};
+  lightdm = handleTest ./lightdm.nix {};
+  lighttpd = handleTest ./lighttpd.nix {};
+  limesurvey = handleTest ./limesurvey.nix {};
+  listmonk = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./listmonk.nix {};
+  litestream = handleTest ./litestream.nix {};
+  lldap = handleTest ./lldap.nix {};
+  locate = handleTest ./locate.nix {};
+  login = handleTest ./login.nix {};
+  logrotate = handleTest ./logrotate.nix {};
+  loki = handleTest ./loki.nix {};
+  luks = handleTest ./luks.nix {};
+  lvm2 = handleTest ./lvm2 {};
+  lxd = pkgs.recurseIntoAttrs (handleTest ./lxd { inherit handleTestOn; });
+  lxd-image-server = handleTest ./lxd-image-server.nix {};
+  #logstash = handleTest ./logstash.nix {};
+  lorri = handleTest ./lorri/default.nix {};
+  maddy = discoverTests (import ./maddy { inherit handleTest; });
+  maestral = handleTest ./maestral.nix {};
+  magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
+  magnetico = handleTest ./magnetico.nix {};
+  mailcatcher = handleTest ./mailcatcher.nix {};
+  mailhog = handleTest ./mailhog.nix {};
+  mailman = handleTest ./mailman.nix {};
+  man = handleTest ./man.nix {};
+  mariadb-galera = handleTest ./mysql/mariadb-galera.nix {};
+  mastodon = discoverTests (import ./web-apps/mastodon { inherit handleTestOn; });
+  pixelfed = discoverTests (import ./web-apps/pixelfed { inherit handleTestOn; });
+  mate = handleTest ./mate.nix {};
+  matomo = handleTest ./matomo.nix {};
+  matrix-appservice-irc = handleTest ./matrix/appservice-irc.nix {};
+  matrix-conduit = handleTest ./matrix/conduit.nix {};
+  matrix-synapse = handleTest ./matrix/synapse.nix {};
+  matrix-synapse-workers = handleTest ./matrix/synapse-workers.nix {};
+  mattermost = handleTest ./mattermost.nix {};
+  mediamtx = handleTest ./mediamtx.nix {};
+  mediatomb = handleTest ./mediatomb.nix {};
+  mediawiki = handleTest ./mediawiki.nix {};
+  meilisearch = handleTest ./meilisearch.nix {};
+  memcached = handleTest ./memcached.nix {};
+  merecat = handleTest ./merecat.nix {};
+  metabase = handleTest ./metabase.nix {};
+  mindustry = handleTest ./mindustry.nix {};
+  minecraft = handleTest ./minecraft.nix {};
+  minecraft-server = handleTest ./minecraft-server.nix {};
+  minidlna = handleTest ./minidlna.nix {};
+  miniflux = handleTest ./miniflux.nix {};
+  minio = handleTest ./minio.nix {};
+  miriway = handleTest ./miriway.nix {};
+  misc = handleTest ./misc.nix {};
+  mjolnir = handleTest ./matrix/mjolnir.nix {};
+  mobilizon = handleTest ./mobilizon.nix {};
+  mod_perl = handleTest ./mod_perl.nix {};
+  molly-brown = handleTest ./molly-brown.nix {};
+  monica = handleTest ./web-apps/monica.nix {};
+  mongodb = handleTest ./mongodb.nix {};
+  moodle = handleTest ./moodle.nix {};
+  moonraker = handleTest ./moonraker.nix {};
+  morph-browser = handleTest ./morph-browser.nix { };
+  morty = handleTest ./morty.nix {};
+  mosquitto = handleTest ./mosquitto.nix {};
+  moosefs = handleTest ./moosefs.nix {};
+  mpd = handleTest ./mpd.nix {};
+  mpv = handleTest ./mpv.nix {};
+  mtp = handleTest ./mtp.nix {};
+  multipass = handleTest ./multipass.nix {};
+  mumble = handleTest ./mumble.nix {};
+  # Fails on aarch64-linux at the PDF creation step - need to debug this on an
+  # aarch64 machine..
+  musescore = handleTestOn ["x86_64-linux"] ./musescore.nix {};
+  munin = handleTest ./munin.nix {};
+  mutableUsers = handleTest ./mutable-users.nix {};
+  mxisd = handleTest ./mxisd.nix {};
+  mympd = handleTest ./mympd.nix {};
+  mysql = handleTest ./mysql/mysql.nix {};
+  mysql-autobackup = handleTest ./mysql/mysql-autobackup.nix {};
+  mysql-backup = handleTest ./mysql/mysql-backup.nix {};
+  mysql-replication = handleTest ./mysql/mysql-replication.nix {};
+  n8n = handleTest ./n8n.nix {};
+  nagios = handleTest ./nagios.nix {};
+  nar-serve = handleTest ./nar-serve.nix {};
+  nat.firewall = handleTest ./nat.nix { withFirewall = true; };
+  nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+  nat.nftables.firewall = handleTest ./nat.nix { withFirewall = true; nftables = true; };
+  nat.nftables.standalone = handleTest ./nat.nix { withFirewall = false; nftables = true; };
+  nats = handleTest ./nats.nix {};
+  navidrome = handleTest ./navidrome.nix {};
+  nbd = handleTest ./nbd.nix {};
+  ncdns = handleTest ./ncdns.nix {};
+  ndppd = handleTest ./ndppd.nix {};
+  nebula = handleTest ./nebula.nix {};
+  netbird = handleTest ./netbird.nix {};
+  neo4j = handleTest ./neo4j.nix {};
+  netdata = handleTest ./netdata.nix {};
+  networking.networkd = handleTest ./networking.nix { networkd = true; };
+  networking.scripted = handleTest ./networking.nix { networkd = false; };
+  netbox_3_6 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_6; };
+  netbox_3_7 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_7; };
+  netbox-upgrade = handleTest ./web-apps/netbox-upgrade.nix {};
+  # TODO: put in networking.nix after the test becomes more complete
+  networkingProxy = handleTest ./networking-proxy.nix {};
+  nextcloud = handleTest ./nextcloud {};
+  nexus = handleTest ./nexus.nix {};
+  # TODO: Test nfsv3 + Kerberos
+  nfs3 = handleTest ./nfs { version = 3; };
+  nfs4 = handleTest ./nfs { version = 4; };
+  nghttpx = handleTest ./nghttpx.nix {};
+  nginx = handleTest ./nginx.nix {};
+  nginx-auth = handleTest ./nginx-auth.nix {};
+  nginx-etag = handleTest ./nginx-etag.nix {};
+  nginx-etag-compression = handleTest ./nginx-etag-compression.nix {};
+  nginx-globalredirect = handleTest ./nginx-globalredirect.nix {};
+  nginx-http3 = handleTest ./nginx-http3.nix {};
+  nginx-modsecurity = handleTest ./nginx-modsecurity.nix {};
+  nginx-moreheaders = handleTest ./nginx-moreheaders.nix {};
+  nginx-njs = handleTest ./nginx-njs.nix {};
+  nginx-proxyprotocol = handleTest ./nginx-proxyprotocol {};
+  nginx-pubhtml = handleTest ./nginx-pubhtml.nix {};
+  nginx-redirectcode = handleTest ./nginx-redirectcode.nix {};
+  nginx-sso = handleTest ./nginx-sso.nix {};
+  nginx-status-page = handleTest ./nginx-status-page.nix {};
+  nginx-tmpdir = handleTest ./nginx-tmpdir.nix {};
+  nginx-unix-socket = handleTest ./nginx-unix-socket.nix {};
+  nginx-variants = handleTest ./nginx-variants.nix {};
+  nifi = handleTestOn ["x86_64-linux"] ./web-apps/nifi.nix {};
+  nitter = handleTest ./nitter.nix {};
+  nix-ld = handleTest ./nix-ld.nix {};
+  nix-serve = handleTest ./nix-serve.nix {};
+  nix-serve-ssh = handleTest ./nix-serve-ssh.nix {};
+  nixops = handleTest ./nixops/default.nix {};
+  nixos-generate-config = handleTest ./nixos-generate-config.nix {};
+  nixos-rebuild-install-bootloader = handleTestOn ["x86_64-linux"] ./nixos-rebuild-install-bootloader.nix {};
+  nixos-rebuild-specialisations = handleTestOn ["x86_64-linux"] ./nixos-rebuild-specialisations.nix {};
+  nixos-rebuild-target-host = handleTest ./nixos-rebuild-target-host.nix {};
+  nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; };
+  nixseparatedebuginfod = handleTest ./nixseparatedebuginfod.nix {};
+  node-red = handleTest ./node-red.nix {};
+  nomad = handleTest ./nomad.nix {};
+  non-default-filesystems = handleTest ./non-default-filesystems.nix {};
+  non-switchable-system = runTest ./non-switchable-system.nix;
+  noto-fonts = handleTest ./noto-fonts.nix {};
+  noto-fonts-cjk-qt-default-weight = handleTest ./noto-fonts-cjk-qt-default-weight.nix {};
+  novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
+  npmrc = handleTest ./npmrc.nix {};
+  nscd = handleTest ./nscd.nix {};
+  nsd = handleTest ./nsd.nix {};
+  ntfy-sh = handleTest ./ntfy-sh.nix {};
+  ntfy-sh-migration = handleTest ./ntfy-sh-migration.nix {};
+  ntpd-rs = handleTest ./ntpd-rs.nix {};
+  nvmetcfg = handleTest ./nvmetcfg.nix {};
+  nzbget = handleTest ./nzbget.nix {};
+  nzbhydra2 = handleTest ./nzbhydra2.nix {};
+  oh-my-zsh = handleTest ./oh-my-zsh.nix {};
+  ombi = handleTest ./ombi.nix {};
+  openarena = handleTest ./openarena.nix {};
+  openldap = handleTest ./openldap.nix {};
+  opensearch = discoverTests (import ./opensearch.nix);
+  openresty-lua = handleTest ./openresty-lua.nix {};
+  opensmtpd = handleTest ./opensmtpd.nix {};
+  opensmtpd-rspamd = handleTest ./opensmtpd-rspamd.nix {};
+  opensnitch = handleTest ./opensnitch.nix {};
+  openssh = handleTest ./openssh.nix {};
+  octoprint = handleTest ./octoprint.nix {};
+  openstack-image-metadata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).metadata or {};
+  openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
+  opentabletdriver = handleTest ./opentabletdriver.nix {};
+  opentelemetry-collector = handleTest ./opentelemetry-collector.nix {};
+  ocsinventory-agent = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./ocsinventory-agent.nix {};
+  owncast = handleTest ./owncast.nix {};
+  outline = handleTest ./outline.nix {};
+  image-contents = handleTest ./image-contents.nix {};
+  openvscode-server = handleTest ./openvscode-server.nix {};
+  orangefs = handleTest ./orangefs.nix {};
+  os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {};
+  osquery = handleTestOn ["x86_64-linux"] ./osquery.nix {};
+  osrm-backend = handleTest ./osrm-backend.nix {};
+  overlayfs = handleTest ./overlayfs.nix {};
+  pacemaker = handleTest ./pacemaker.nix {};
+  packagekit = handleTest ./packagekit.nix {};
+  pam-file-contents = handleTest ./pam/pam-file-contents.nix {};
+  pam-oath-login = handleTest ./pam/pam-oath-login.nix {};
+  pam-u2f = handleTest ./pam/pam-u2f.nix {};
+  pam-ussh = handleTest ./pam/pam-ussh.nix {};
+  pam-zfs-key = handleTest ./pam/zfs-key.nix {};
+  pass-secret-service = handleTest ./pass-secret-service.nix {};
+  patroni = handleTestOn ["x86_64-linux"] ./patroni.nix {};
+  pantalaimon = handleTest ./matrix/pantalaimon.nix {};
+  pantheon = handleTest ./pantheon.nix {};
+  paperless = handleTest ./paperless.nix {};
+  parsedmarc = handleTest ./parsedmarc {};
+  pdns-recursor = handleTest ./pdns-recursor.nix {};
+  peerflix = handleTest ./peerflix.nix {};
+  peering-manager = handleTest ./web-apps/peering-manager.nix {};
+  peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {};
+  peroxide = handleTest ./peroxide.nix {};
+  pgadmin4 = handleTest ./pgadmin4.nix {};
+  pgbouncer = handleTest ./pgbouncer.nix {};
+  pgjwt = handleTest ./pgjwt.nix {};
+  pgmanage = handleTest ./pgmanage.nix {};
+  phosh = handleTest ./phosh.nix {};
+  photoprism = handleTest ./photoprism.nix {};
+  php = handleTest ./php {};
+  php81 = handleTest ./php { php = pkgs.php81; };
+  php82 = handleTest ./php { php = pkgs.php82; };
+  php83 = handleTest ./php { php = pkgs.php83; };
+  phylactery = handleTest ./web-apps/phylactery.nix {};
+  pict-rs = handleTest ./pict-rs.nix {};
+  pinnwand = handleTest ./pinnwand.nix {};
+  plantuml-server = handleTest ./plantuml-server.nix {};
+  plasma-bigscreen = handleTest ./plasma-bigscreen.nix {};
+  plasma5 = handleTest ./plasma5.nix {};
+  plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {};
+  plausible = handleTest ./plausible.nix {};
+  please = handleTest ./please.nix {};
+  pleroma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./pleroma.nix {};
+  plikd = handleTest ./plikd.nix {};
+  plotinus = handleTest ./plotinus.nix {};
+  podgrab = handleTest ./podgrab.nix {};
+  podman = handleTestOn ["aarch64-linux" "x86_64-linux"] ./podman/default.nix {};
+  podman-tls-ghostunnel = handleTestOn ["aarch64-linux" "x86_64-linux"] ./podman/tls-ghostunnel.nix {};
+  polaris = handleTest ./polaris.nix {};
+  pomerium = handleTestOn ["x86_64-linux"] ./pomerium.nix {};
+  portunus = handleTest ./portunus.nix { };
+  postfix = handleTest ./postfix.nix {};
+  postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {};
+  postfixadmin = handleTest ./postfixadmin.nix {};
+  postgis = handleTest ./postgis.nix {};
+  apache_datasketches = handleTest ./apache_datasketches.nix {};
+  postgresql = handleTest ./postgresql.nix {};
+  postgresql-jit = handleTest ./postgresql-jit.nix {};
+  postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
+  powerdns = handleTest ./powerdns.nix {};
+  powerdns-admin = handleTest ./powerdns-admin.nix {};
+  power-profiles-daemon = handleTest ./power-profiles-daemon.nix {};
+  pppd = handleTest ./pppd.nix {};
+  predictable-interface-names = handleTest ./predictable-interface-names.nix {};
+  pretalx = runTest ./web-apps/pretalx.nix;
+  printing-socket = handleTest ./printing.nix { socket = true; };
+  printing-service = handleTest ./printing.nix { socket = false; };
+  privoxy = handleTest ./privoxy.nix {};
+  prometheus = handleTest ./prometheus.nix {};
+  prometheus-exporters = handleTest ./prometheus-exporters.nix {};
+  prosody = handleTest ./xmpp/prosody.nix {};
+  prosody-mysql = handleTest ./xmpp/prosody-mysql.nix {};
+  proxy = handleTest ./proxy.nix {};
+  prowlarr = handleTest ./prowlarr.nix {};
+  pt2-clone = handleTest ./pt2-clone.nix {};
+  pykms = handleTest ./pykms.nix {};
+  public-inbox = handleTest ./public-inbox.nix {};
+  pufferpanel = handleTest ./pufferpanel.nix {};
+  pulseaudio = discoverTests (import ./pulseaudio.nix);
+  qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
+  qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
+  qemu-vm-volatile-root = runTest ./qemu-vm-volatile-root.nix;
+  qemu-vm-external-disk-image = runTest ./qemu-vm-external-disk-image.nix;
+  qgis = handleTest ./qgis.nix { qgisPackage = pkgs.qgis; };
+  qgis-ltr = handleTest ./qgis.nix { qgisPackage = pkgs.qgis-ltr; };
+  qownnotes = handleTest ./qownnotes.nix {};
+  quake3 = handleTest ./quake3.nix {};
+  quicktun = handleTest ./quicktun.nix {};
+  quorum = handleTest ./quorum.nix {};
+  rabbitmq = handleTest ./rabbitmq.nix {};
+  radarr = handleTest ./radarr.nix {};
+  radicale = handleTest ./radicale.nix {};
+  ragnarwm = handleTest ./ragnarwm.nix {};
+  rasdaemon = handleTest ./rasdaemon.nix {};
+  readarr = handleTest ./readarr.nix {};
+  redis = handleTest ./redis.nix {};
+  redmine = handleTest ./redmine.nix {};
+  restartByActivationScript = handleTest ./restart-by-activation-script.nix {};
+  restic = handleTest ./restic.nix {};
+  retroarch = handleTest ./retroarch.nix {};
+  rkvm = handleTest ./rkvm {};
+  robustirc-bridge = handleTest ./robustirc-bridge.nix {};
+  roundcube = handleTest ./roundcube.nix {};
+  rosenpass = handleTest ./rosenpass.nix {};
+  rshim = handleTest ./rshim.nix {};
+  rspamd = handleTest ./rspamd.nix {};
+  rspamd-trainer = handleTest ./rspamd-trainer.nix {};
+  rss2email = handleTest ./rss2email.nix {};
+  rstudio-server = handleTest ./rstudio-server.nix {};
+  rsyncd = handleTest ./rsyncd.nix {};
+  rsyslogd = handleTest ./rsyslogd.nix {};
+  rxe = handleTest ./rxe.nix {};
+  sabnzbd = handleTest ./sabnzbd.nix {};
+  samba = handleTest ./samba.nix {};
+  samba-wsdd = handleTest ./samba-wsdd.nix {};
+  sane = handleTest ./sane.nix {};
+  sanoid = handleTest ./sanoid.nix {};
+  scaphandre = handleTest ./scaphandre.nix {};
+  schleuder = handleTest ./schleuder.nix {};
+  scrutiny = handleTest ./scrutiny.nix {};
+  sddm = handleTest ./sddm.nix {};
+  seafile = handleTest ./seafile.nix {};
+  searx = handleTest ./searx.nix {};
+  seatd = handleTest ./seatd.nix {};
+  service-runner = handleTest ./service-runner.nix {};
+  sftpgo = runTest ./sftpgo.nix;
+  sfxr-qt = handleTest ./sfxr-qt.nix {};
+  sgt-puzzles = handleTest ./sgt-puzzles.nix {};
+  shadow = handleTest ./shadow.nix {};
+  shadowsocks = handleTest ./shadowsocks {};
+  shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
+  shiori = handleTest ./shiori.nix {};
+  signal-desktop = handleTest ./signal-desktop.nix {};
+  simple = handleTest ./simple.nix {};
+  sing-box = handleTest ./sing-box.nix {};
+  slimserver = handleTest ./slimserver.nix {};
+  slurm = handleTest ./slurm.nix {};
+  snmpd = handleTest ./snmpd.nix {};
+  smokeping = handleTest ./smokeping.nix {};
+  snapcast = handleTest ./snapcast.nix {};
+  snapper = handleTest ./snapper.nix {};
+  snipe-it = runTest ./web-apps/snipe-it.nix;
+  soapui = handleTest ./soapui.nix {};
+  soft-serve = handleTest ./soft-serve.nix {};
+  sogo = handleTest ./sogo.nix {};
+  solanum = handleTest ./solanum.nix {};
+  sonarr = handleTest ./sonarr.nix {};
+  sonic-server = handleTest ./sonic-server.nix {};
+  sourcehut = handleTest ./sourcehut {};
+  spacecookie = handleTest ./spacecookie.nix {};
+  spark = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./spark {};
+  sqlite3-to-mysql = handleTest ./sqlite3-to-mysql.nix {};
+  sslh = handleTest ./sslh.nix {};
+  ssh-agent-auth = handleTest ./ssh-agent-auth.nix {};
+  ssh-audit = handleTest ./ssh-audit.nix {};
+  sssd = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./sssd.nix {};
+  sssd-ldap = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./sssd-ldap.nix {};
+  stalwart-mail = handleTest ./stalwart-mail.nix {};
+  stargazer = runTest ./web-servers/stargazer.nix;
+  starship = handleTest ./starship.nix {};
+  static-web-server = handleTest ./web-servers/static-web-server.nix {};
+  step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
+  stratis = handleTest ./stratis {};
+  strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
+  stub-ld = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./stub-ld.nix {};
+  stunnel = handleTest ./stunnel.nix {};
+  sudo = handleTest ./sudo.nix {};
+  sudo-rs = handleTest ./sudo-rs.nix {};
+  suwayomi-server = handleTest ./suwayomi-server.nix {};
+  swap-file-btrfs = handleTest ./swap-file-btrfs.nix {};
+  swap-partition = handleTest ./swap-partition.nix {};
+  swap-random-encryption = handleTest ./swap-random-encryption.nix {};
+  sway = handleTest ./sway.nix {};
+  switchTest = handleTest ./switch-test.nix {};
+  sympa = handleTest ./sympa.nix {};
+  syncthing = handleTest ./syncthing.nix {};
+  syncthing-no-settings = handleTest ./syncthing-no-settings.nix {};
+  syncthing-init = handleTest ./syncthing-init.nix {};
+  syncthing-many-devices = handleTest ./syncthing-many-devices.nix {};
+  syncthing-relay = handleTest ./syncthing-relay.nix {};
+  sysinit-reactivation = runTest ./sysinit-reactivation.nix;
+  systemd = handleTest ./systemd.nix {};
+  systemd-analyze = handleTest ./systemd-analyze.nix {};
+  systemd-binfmt = handleTestOn ["x86_64-linux"] ./systemd-binfmt.nix {};
+  systemd-boot = handleTest ./systemd-boot.nix {};
+  systemd-bpf = handleTest ./systemd-bpf.nix {};
+  systemd-confinement = handleTest ./systemd-confinement.nix {};
+  systemd-coredump = handleTest ./systemd-coredump.nix {};
+  systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
+  systemd-credentials-tpm2 = handleTest ./systemd-credentials-tpm2.nix {};
+  systemd-escaping = handleTest ./systemd-escaping.nix {};
+  systemd-initrd-bridge = handleTest ./systemd-initrd-bridge.nix {};
+  systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {};
+  systemd-initrd-luks-fido2 = handleTest ./systemd-initrd-luks-fido2.nix {};
+  systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {};
+  systemd-initrd-luks-empty-passphrase = handleTest ./initrd-luks-empty-passphrase.nix { systemdStage1 = true; };
+  systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {};
+  systemd-initrd-luks-tpm2 = handleTest ./systemd-initrd-luks-tpm2.nix {};
+  systemd-initrd-luks-unl0kr = handleTest ./systemd-initrd-luks-unl0kr.nix {};
+  systemd-initrd-modprobe = handleTest ./systemd-initrd-modprobe.nix {};
+  systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; };
+  systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {};
+  systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {};
+  systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {};
+  systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {};
+  systemd-initrd-networkd-ssh = handleTest ./systemd-initrd-networkd-ssh.nix {};
+  systemd-initrd-networkd-openvpn = handleTestOn [ "x86_64-linux" "i686-linux" ] ./initrd-network-openvpn { systemdStage1 = true; };
+  systemd-initrd-vlan = handleTest ./systemd-initrd-vlan.nix {};
+  systemd-journal = handleTest ./systemd-journal.nix {};
+  systemd-journal-gateway = handleTest ./systemd-journal-gateway.nix {};
+  systemd-journal-upload = handleTest ./systemd-journal-upload.nix {};
+  systemd-lock-handler = runTestOn ["aarch64-linux" "x86_64-linux"] ./systemd-lock-handler.nix;
+  systemd-machinectl = handleTest ./systemd-machinectl.nix {};
+  systemd-networkd = handleTest ./systemd-networkd.nix {};
+  systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {};
+  systemd-networkd-dhcpserver-static-leases = handleTest ./systemd-networkd-dhcpserver-static-leases.nix {};
+  systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {};
+  systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
+  systemd-no-tainted = handleTest ./systemd-no-tainted.nix {};
+  systemd-nspawn = handleTest ./systemd-nspawn.nix {};
+  systemd-nspawn-configfile = handleTest ./systemd-nspawn-configfile.nix {};
+  systemd-oomd = handleTest ./systemd-oomd.nix {};
+  systemd-portabled = handleTest ./systemd-portabled.nix {};
+  systemd-repart = handleTest ./systemd-repart.nix {};
+  systemd-shutdown = handleTest ./systemd-shutdown.nix {};
+  systemd-sysupdate = runTest ./systemd-sysupdate.nix;
+  systemd-sysusers-mutable = runTest ./systemd-sysusers-mutable.nix;
+  systemd-sysusers-immutable = runTest ./systemd-sysusers-immutable.nix;
+  systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
+  systemd-timesyncd-nscd-dnssec = handleTest ./systemd-timesyncd-nscd-dnssec.nix {};
+  systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {};
+  systemd-misc = handleTest ./systemd-misc.nix {};
+  systemd-userdbd = handleTest ./systemd-userdbd.nix {};
+  systemd-homed = handleTest ./systemd-homed.nix {};
+  systemtap = handleTest ./systemtap.nix {};
+  tandoor-recipes = handleTest ./tandoor-recipes.nix {};
+  tang = handleTest ./tang.nix {};
+  taskserver = handleTest ./taskserver.nix {};
+  tayga = handleTest ./tayga.nix {};
+  teeworlds = handleTest ./teeworlds.nix {};
+  telegraf = handleTest ./telegraf.nix {};
+  teleport = handleTest ./teleport.nix {};
+  thelounge = handleTest ./thelounge.nix {};
+  terminal-emulators = handleTest ./terminal-emulators.nix {};
+  tiddlywiki = handleTest ./tiddlywiki.nix {};
+  tigervnc = handleTest ./tigervnc.nix {};
+  timescaledb = handleTest ./timescaledb.nix {};
+  promscale = handleTest ./promscale.nix {};
+  timezone = handleTest ./timezone.nix {};
+  tinc = handleTest ./tinc {};
+  tinydns = handleTest ./tinydns.nix {};
+  tinyproxy = handleTest ./tinyproxy.nix {};
+  tinywl = handleTest ./tinywl.nix {};
+  tmate-ssh-server = handleTest ./tmate-ssh-server.nix { };
+  tomcat = handleTest ./tomcat.nix {};
+  tor = handleTest ./tor.nix {};
+  traefik = handleTestOn ["aarch64-linux" "x86_64-linux"] ./traefik.nix {};
+  trafficserver = handleTest ./trafficserver.nix {};
+  transmission = handleTest ./transmission.nix { transmission = pkgs.transmission; };
+  transmission_4 = handleTest ./transmission.nix { transmission = pkgs.transmission_4; };
+  # tracee requires bpf
+  tracee = handleTestOn ["x86_64-linux"] ./tracee.nix {};
+  trezord = handleTest ./trezord.nix {};
+  trickster = handleTest ./trickster.nix {};
+  trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {};
+  tsja = handleTest ./tsja.nix {};
+  tsm-client-gui = handleTest ./tsm-client-gui.nix {};
+  ttyd = handleTest ./web-servers/ttyd.nix {};
+  txredisapi = handleTest ./txredisapi.nix {};
+  tuptime = handleTest ./tuptime.nix {};
+  turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {};
+  tuxguitar = handleTest ./tuxguitar.nix {};
+  twingate = runTest ./twingate.nix;
+  typesense = handleTest ./typesense.nix {};
+  ucarp = handleTest ./ucarp.nix {};
+  udisks2 = handleTest ./udisks2.nix {};
+  ulogd = handleTest ./ulogd/ulogd.nix {};
+  unbound = handleTest ./unbound.nix {};
+  unifi = handleTest ./unifi.nix {};
+  unit-php = handleTest ./web-servers/unit-php.nix {};
+  upnp.iptables = handleTest ./upnp.nix { useNftables = false; };
+  upnp.nftables = handleTest ./upnp.nix { useNftables = true; };
+  uptermd = handleTest ./uptermd.nix {};
+  uptime-kuma = handleTest ./uptime-kuma.nix {};
+  urn-timer = handleTest ./urn-timer.nix {};
+  usbguard = handleTest ./usbguard.nix {};
+  user-activation-scripts = handleTest ./user-activation-scripts.nix {};
+  user-expiry = runTest ./user-expiry.nix;
+  user-home-mode = handleTest ./user-home-mode.nix {};
+  uwsgi = handleTest ./uwsgi.nix {};
+  v2ray = handleTest ./v2ray.nix {};
+  varnish60 = handleTest ./varnish.nix { package = pkgs.varnish60; };
+  varnish74 = handleTest ./varnish.nix { package = pkgs.varnish74; };
+  vault = handleTest ./vault.nix {};
+  vault-agent = handleTest ./vault-agent.nix {};
+  vault-dev = handleTest ./vault-dev.nix {};
+  vault-postgresql = handleTest ./vault-postgresql.nix {};
+  vaultwarden = handleTest ./vaultwarden.nix {};
+  vector = handleTest ./vector.nix {};
+  vengi-tools = handleTest ./vengi-tools.nix {};
+  victoriametrics = handleTest ./victoriametrics.nix {};
+  vikunja = handleTest ./vikunja.nix {};
+  virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
+  vscode-remote-ssh = handleTestOn ["x86_64-linux"] ./vscode-remote-ssh.nix {};
+  vscodium = discoverTests (import ./vscodium.nix);
+  vsftpd = handleTest ./vsftpd.nix {};
+  warzone2100 = handleTest ./warzone2100.nix {};
+  wasabibackend = handleTest ./wasabibackend.nix {};
+  watchdogd = handleTest ./watchdogd.nix {};
+  webhook = runTest ./webhook.nix;
+  wiki-js = handleTest ./wiki-js.nix {};
+  wine = handleTest ./wine.nix {};
+  wireguard = handleTest ./wireguard {};
+  without-nix = handleTest ./without-nix.nix {};
+  wmderland = handleTest ./wmderland.nix {};
+  wpa_supplicant = handleTest ./wpa_supplicant.nix {};
+  wordpress = handleTest ./wordpress.nix {};
+  wrappers = handleTest ./wrappers.nix {};
+  writefreely = handleTest ./web-apps/writefreely.nix {};
+  xandikos = handleTest ./xandikos.nix {};
+  xautolock = handleTest ./xautolock.nix {};
+  xfce = handleTest ./xfce.nix {};
+  xmonad = handleTest ./xmonad.nix {};
+  xmonad-xdg-autostart = handleTest ./xmonad-xdg-autostart.nix {};
+  xpadneo = handleTest ./xpadneo.nix {};
+  xrdp = handleTest ./xrdp.nix {};
+  xrdp-with-audio-pulseaudio = handleTest ./xrdp-with-audio-pulseaudio.nix {};
+  xscreensaver = handleTest ./xscreensaver.nix {};
+  xss-lock = handleTest ./xss-lock.nix {};
+  xterm = handleTest ./xterm.nix {};
+  xxh = handleTest ./xxh.nix {};
+  yabar = handleTest ./yabar.nix {};
+  yggdrasil = handleTest ./yggdrasil.nix {};
+  zammad = handleTest ./zammad.nix {};
+  zeronet-conservancy = handleTest ./zeronet-conservancy.nix {};
+  zfs = handleTest ./zfs.nix {};
+  zigbee2mqtt = handleTest ./zigbee2mqtt.nix {};
+  zoneminder = handleTest ./zoneminder.nix {};
+  zookeeper = handleTest ./zookeeper.nix {};
+  zram-generator = handleTest ./zram-generator.nix {};
+  zrepl = handleTest ./zrepl.nix {};
+  zsh-history = handleTest ./zsh-history.nix {};
+  zwave-js = handleTest ./zwave-js.nix {};
+}
diff --git a/nixpkgs/nixos/tests/alps.nix b/nixpkgs/nixos/tests/alps.nix
new file mode 100644
index 000000000000..9756f2d4da15
--- /dev/null
+++ b/nixpkgs/nixos/tests/alps.nix
@@ -0,0 +1,108 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "alps";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hmenke ];
+  };
+
+  nodes = {
+    server = {
+      imports = [ ./common/user-account.nix ];
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+      networking.extraHosts = ''
+        127.0.0.1 ${domain}
+      '';
+      networking.firewall.allowedTCPPorts = [ 25 465 993 ];
+      services.postfix = {
+        enable = true;
+        enableSubmission = true;
+        enableSubmissions = true;
+        tlsTrustedAuthorities = "${certs.ca.cert}";
+        sslCert = "${certs.${domain}.cert}";
+        sslKey = "${certs.${domain}.key}";
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        sslCACert = "${certs.ca.cert}";
+        sslServerCert = "${certs.${domain}.cert}";
+        sslServerKey = "${certs.${domain}.key}";
+      };
+    };
+
+    client = { nodes, config, ... }: {
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+      networking.extraHosts = ''
+        ${nodes.server.config.networking.primaryIPAddress} ${domain}
+      '';
+      services.alps = {
+        enable = true;
+        theme = "alps";
+        imaps = {
+          host = domain;
+          port = 993;
+        };
+        smtps = {
+          host = domain;
+          port = 465;
+        };
+      };
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "test-alps-login" { } ''
+          from urllib.request import build_opener, HTTPCookieProcessor, Request
+          from urllib.parse import urlencode, urljoin
+          from http.cookiejar import CookieJar
+
+          baseurl = "http://localhost:${toString config.services.alps.port}"
+          username = "alice"
+          password = "${nodes.server.config.users.users.alice.password}"
+          cookiejar = CookieJar()
+          cookieprocessor = HTTPCookieProcessor(cookiejar)
+          opener = build_opener(cookieprocessor)
+
+          data = urlencode({"username": username, "password": password}).encode()
+          req = Request(urljoin(baseurl, "login"), data=data, method="POST")
+          with opener.open(req) as ret:
+              # Check that the alps_session cookie is set
+              print(cookiejar)
+              assert any(cookie.name == "alps_session" for cookie in cookiejar)
+
+          req = Request(baseurl)
+          with opener.open(req) as ret:
+              # Check that the alps_session cookie is still there...
+              print(cookiejar)
+              assert any(cookie.name == "alps_session" for cookie in cookiejar)
+              # ...and that we have not been redirected back to the login page
+              print(ret.url)
+              assert ret.url == urljoin(baseurl, "mailbox/INBOX")
+
+          req = Request(urljoin(baseurl, "logout"))
+          with opener.open(req) as ret:
+              # Check that the alps_session cookie is now gone
+              print(cookiejar)
+              assert all(cookie.name != "alps_session" for cookie in cookiejar)
+        '')
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    server.start()
+    server.wait_for_unit("postfix.service")
+    server.wait_for_unit("dovecot2.service")
+    server.wait_for_open_port(465)
+    server.wait_for_open_port(993)
+
+    client.start()
+    client.wait_for_unit("alps.service")
+    client.wait_for_open_port(${toString nodes.client.config.services.alps.port})
+    client.succeed("test-alps-login")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/amazon-init-shell.nix b/nixpkgs/nixos/tests/amazon-init-shell.nix
new file mode 100644
index 000000000000..3c040841b6d2
--- /dev/null
+++ b/nixpkgs/nixos/tests/amazon-init-shell.nix
@@ -0,0 +1,40 @@
+# This test verifies that the amazon-init service can treat the `user-data` ec2
+# metadata file as a shell script. If amazon-init detects that `user-data` is a
+# script (based on the presence of the shebang #! line) it executes it and
+# exits.
+# Note that other tests verify that amazon-init can treat user-data as a nixos
+# configuration expression.
+
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+makeTest {
+  name = "amazon-init";
+  meta = with maintainers; {
+    maintainers = [ urbas ];
+  };
+  nodes.machine = { ... }:
+  {
+    imports = [ ../modules/profiles/headless.nix ../modules/virtualisation/amazon-init.nix ];
+    services.openssh.enable = true;
+    networking.hostName = "";
+    environment.etc."ec2-metadata/user-data" = {
+      text = ''
+        #!/usr/bin/bash
+
+        echo successful > /tmp/evidence
+      '';
+    };
+  };
+  testScript = ''
+    # To wait until amazon-init terminates its run
+    unnamed.wait_for_unit("amazon-init.service")
+
+    unnamed.succeed("grep -q successful /tmp/evidence")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/amazon-ssm-agent.nix b/nixpkgs/nixos/tests/amazon-ssm-agent.nix
new file mode 100644
index 000000000000..957e9e0e02c5
--- /dev/null
+++ b/nixpkgs/nixos/tests/amazon-ssm-agent.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "amazon-ssm-agent";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.amazon-ssm-agent.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_file("/etc/amazon/ssm/seelog.xml")
+    machine.wait_for_file("/etc/amazon/ssm/amazon-ssm-agent.json")
+
+    machine.wait_for_unit("amazon-ssm-agent.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/amd-sev.nix b/nixpkgs/nixos/tests/amd-sev.nix
new file mode 100644
index 000000000000..bf9a50c10d0d
--- /dev/null
+++ b/nixpkgs/nixos/tests/amd-sev.nix
@@ -0,0 +1,56 @@
+{ lib, ... }: {
+  name = "amd-sev";
+  meta = {
+    maintainers = with lib.maintainers; [ trundle veehaitch ];
+  };
+
+  nodes.machine = { lib, ... }: {
+    hardware.cpu.amd.sev.enable = true;
+    hardware.cpu.amd.sevGuest.enable = true;
+
+    specialisation.sevCustomUserGroup.configuration = {
+      users.groups.sevtest = { };
+
+      hardware.cpu.amd.sev = {
+        enable = true;
+        group = "root";
+        mode = "0600";
+      };
+      hardware.cpu.amd.sevGuest = {
+        enable = true;
+        group = "sevtest";
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+    in
+    ''
+      machine.wait_for_unit("multi-user.target")
+
+      with subtest("Check default settings"):
+        out = machine.succeed("cat /etc/udev/rules.d/99-local.rules")
+        assert 'KERNEL=="sev", OWNER="root", GROUP="sev", MODE="0660"' in out
+        assert 'KERNEL=="sev-guest", OWNER="root", GROUP="sev-guest", MODE="0660"' in out
+
+        out = machine.succeed("cat /etc/group")
+        assert "sev:" in out
+        assert "sev-guest:" in out
+        assert "sevtest:" not in out
+
+      with subtest("Activate configuration with custom user/group"):
+        machine.succeed('${specialisations}/sevCustomUserGroup/bin/switch-to-configuration test')
+
+      with subtest("Check custom user and group"):
+        out = machine.succeed("cat /etc/udev/rules.d/99-local.rules")
+        assert 'KERNEL=="sev", OWNER="root", GROUP="root", MODE="0600"' in out
+        assert 'KERNEL=="sev-guest", OWNER="root", GROUP="sevtest", MODE="0660"' in out
+
+        out = machine.succeed("cat /etc/group")
+        assert "sev:" not in out
+        assert "sev-guest:" not in out
+        assert "sevtest:" in out
+    '';
+}
diff --git a/nixpkgs/nixos/tests/anbox.nix b/nixpkgs/nixos/tests/anbox.nix
new file mode 100644
index 000000000000..a00116536db7
--- /dev/null
+++ b/nixpkgs/nixos/tests/anbox.nix
@@ -0,0 +1,36 @@
+{ lib, pkgs, ... }:
+
+{
+  name = "anbox";
+  meta.maintainers = with lib.maintainers; [ mvnetbiz ];
+
+  nodes.machine = { pkgs, config, ... }: {
+    imports = [
+      ./common/user-account.nix
+      ./common/x11.nix
+    ];
+
+    environment.systemPackages = with pkgs; [ android-tools ];
+
+    test-support.displayManager.auto.user = "alice";
+
+    virtualisation.anbox.enable = true;
+    boot.kernelPackages = pkgs.linuxKernel.packages.linux_5_15;
+    virtualisation.memorySize = 2500;
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
+  in ''
+    machine.wait_for_x()
+
+    machine.wait_until_succeeds(
+        "sudo -iu alice ${bus} anbox wait-ready"
+    )
+
+    machine.wait_until_succeeds("adb shell true")
+
+    print(machine.succeed("adb devices"))
+  '';
+}
diff --git a/nixpkgs/nixos/tests/angie-api.nix b/nixpkgs/nixos/tests/angie-api.nix
new file mode 100644
index 000000000000..4c8d6b54247b
--- /dev/null
+++ b/nixpkgs/nixos/tests/angie-api.nix
@@ -0,0 +1,148 @@
+import ./make-test-python.nix ({lib, pkgs, ...}:
+let
+  hosts = ''
+    192.168.2.101 example.com
+    192.168.2.101 api.example.com
+    192.168.2.101 backend.example.com
+  '';
+
+in
+{
+  name = "angie-api";
+  meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
+
+  nodes = {
+    server = { pkgs, ... }: {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.101"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [ 80 ];
+      };
+
+      services.nginx = {
+        enable = true;
+        package = pkgs.angie;
+
+        upstreams = {
+          "backend-http" = {
+            servers = { "backend.example.com:8080" = { fail_timeout = "0"; }; };
+            extraConfig = ''
+              zone upstream 256k;
+            '';
+          };
+          "backend-socket" = {
+            servers = { "unix:/run/example.sock" = { fail_timeout = "0"; }; };
+            extraConfig = ''
+              zone upstream 256k;
+            '';
+          };
+        };
+
+        virtualHosts."api.example.com" = {
+          locations."/console/" = {
+            extraConfig = ''
+              api /status/;
+
+              allow 192.168.2.201;
+              deny all;
+            '';
+          };
+        };
+
+        virtualHosts."example.com" = {
+          locations."/test/" = {
+            root = lib.mkForce (pkgs.runCommandLocal "testdir" {} ''
+              mkdir -p "$out/test"
+              cat > "$out/test/index.html" <<EOF
+              <html><body>Hello World!</body></html>
+              EOF
+            '');
+            extraConfig = ''
+              status_zone test_zone;
+
+              allow 192.168.2.201;
+              deny all;
+            '';
+          };
+          locations."/test/locked/" = {
+            extraConfig = ''
+              status_zone test_zone;
+
+              deny all;
+            '';
+          };
+          locations."/test/error/" = {
+            extraConfig = ''
+              status_zone test_zone;
+
+              allow all;
+            '';
+          };
+          locations."/upstream-http/" = {
+            proxyPass = "http://backend-http";
+          };
+          locations."/upstream-socket/" = {
+            proxyPass = "http://backend-socket";
+          };
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.201"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("nginx")
+    server.wait_for_open_port(80)
+
+    # Check Angie version
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.angie.version' | grep '${pkgs.angie.version}'")
+
+    # Check access
+    client.succeed("curl --verbose --head http://api.example.com/console/ | grep 'HTTP/1.1 200'")
+    server.succeed("curl --verbose --head http://api.example.com/console/ | grep 'HTTP/1.1 403 Forbidden'")
+
+    # Check responses and requests
+    client.succeed("curl --verbose http://example.com/test/")
+    client.succeed("curl --verbose http://example.com/test/locked/")
+    client.succeed("curl --verbose http://example.com/test/locked/")
+    client.succeed("curl --verbose http://example.com/test/error/")
+    client.succeed("curl --verbose http://example.com/test/error/")
+    client.succeed("curl --verbose http://example.com/test/error/")
+    server.succeed("curl --verbose http://example.com/test/")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.location_zones.test_zone.responses.\"200\"' | grep '1'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.location_zones.test_zone.responses.\"403\"' | grep '3'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.location_zones.test_zone.responses.\"404\"' | grep '3'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.location_zones.test_zone.requests.total' | grep '7'")
+
+    # Check upstreams
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-http\".peers.\"192.168.2.101:8080\".state' | grep 'up'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-http\".peers.\"192.168.2.101:8080\".health.fails' | grep '0'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-socket\".peers.\"unix:/run/example.sock\".state' | grep 'up'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-socket\".peers.\"unix:/run/example.sock\".health.fails' | grep '0'")
+    client.succeed("curl --verbose http://example.com/upstream-http/")
+    client.succeed("curl --verbose http://example.com/upstream-socket/")
+    client.succeed("curl --verbose http://example.com/upstream-socket/")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-http\".peers.\"192.168.2.101:8080\".health.fails' | grep '1'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-socket\".peers.\"unix:/run/example.sock\".health.fails' | grep '2'")
+
+    server.shutdown()
+    client.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/anki-sync-server.nix b/nixpkgs/nixos/tests/anki-sync-server.nix
new file mode 100644
index 000000000000..7d08cc9cb878
--- /dev/null
+++ b/nixpkgs/nixos/tests/anki-sync-server.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    ankiSyncTest = pkgs.writeScript "anki-sync-test.py" ''
+      #!${pkgs.python3}/bin/python
+
+      import sys
+
+      # get site paths from anki itself
+      from runpy import run_path
+      run_path("${pkgs.anki}/bin/.anki-wrapped")
+      import anki
+
+      col = anki.collection.Collection('test_collection')
+      endpoint = 'http://localhost:27701'
+
+      # Sanity check: verify bad login fails
+      try:
+         col.sync_login('baduser', 'badpass', endpoint)
+         print("bad user login worked?!")
+         sys.exit(1)
+      except anki.errors.SyncError:
+          pass
+
+      # test logging in to users
+      col.sync_login('user', 'password', endpoint)
+      col.sync_login('passfileuser', 'passfilepassword', endpoint)
+
+      # Test actual sync. login apparently doesn't remember the endpoint...
+      login = col.sync_login('user', 'password', endpoint)
+      login.endpoint = endpoint
+      sync = col.sync_collection(login, False)
+      assert sync.required == sync.NO_CHANGES
+      # TODO: create an archive with server content including a test card
+      # and check we got it?
+    '';
+    testPasswordFile = pkgs.writeText "anki-password" "passfilepassword";
+  in
+  {
+  name = "anki-sync-server";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ martinetd ];
+  };
+
+  nodes.machine = { pkgs, ...}: {
+    services.anki-sync-server = {
+      enable = true;
+      users = [
+        { username = "user";
+          password = "password";
+        }
+        { username = "passfileuser";
+          passwordFile = testPasswordFile;
+        }
+      ];
+    };
+  };
+
+
+  testScript =
+    ''
+      start_all()
+
+      with subtest("Server starts successfully"):
+          # service won't start without users
+          machine.wait_for_unit("anki-sync-server.service")
+          machine.wait_for_open_port(27701)
+
+      with subtest("Can sync"):
+          machine.succeed("${ankiSyncTest}")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/anuko-time-tracker.nix b/nixpkgs/nixos/tests/anuko-time-tracker.nix
new file mode 100644
index 000000000000..18c3bf5cf695
--- /dev/null
+++ b/nixpkgs/nixos/tests/anuko-time-tracker.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "anuko-time-tracker";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ michaelshmitty ];
+  };
+  nodes = {
+    machine = {
+      services.anuko-time-tracker.enable = true;
+    };
+  };
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("phpfpm-anuko-time-tracker")
+    machine.wait_for_open_port(80);
+    machine.wait_until_succeeds("curl -s --fail -L http://localhost/time.php | grep 'Anuko Time Tracker'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/apache_datasketches.nix b/nixpkgs/nixos/tests/apache_datasketches.nix
new file mode 100644
index 000000000000..2bf099ac7991
--- /dev/null
+++ b/nixpkgs/nixos/tests/apache_datasketches.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "postgis";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lsix ]; # TODO: Who's the maintener now?
+  };
+
+  nodes = {
+    master =
+      { pkgs, ... }:
+
+      {
+        services.postgresql = let mypg = pkgs.postgresql_15; in {
+            enable = true;
+            package = mypg;
+            extraPlugins = with mypg.pkgs; [
+              apache_datasketches
+            ];
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    master.wait_for_unit("postgresql")
+    master.sleep(10)  # Hopefully this is long enough!!
+    master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION datasketches;'")
+    master.succeed("sudo -u postgres psql -c 'SELECT hll_sketch_to_string(hll_sketch_build(1));'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/apcupsd.nix b/nixpkgs/nixos/tests/apcupsd.nix
new file mode 100644
index 000000000000..287140f039d8
--- /dev/null
+++ b/nixpkgs/nixos/tests/apcupsd.nix
@@ -0,0 +1,41 @@
+let
+  # arbitrary address
+  ipAddr = "192.168.42.42";
+in
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "apcupsd";
+  meta.maintainers = with lib.maintainers; [ bjornfor ];
+
+  nodes = {
+    machine = {
+      services.apcupsd = {
+        enable = true;
+        configText = ''
+          UPSTYPE usb
+          BATTERYLEVEL 42
+          # Configure NISIP so that the only way apcaccess can work is to read
+          # this config.
+          NISIP ${ipAddr}
+        '';
+      };
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [{
+          address = ipAddr;
+          prefixLength = 24;
+        }];
+      };
+    };
+  };
+
+  # Check that the service starts, that the CLI (apcaccess) works and that it
+  # uses the config (ipAddr) defined in the service config.
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("apcupsd.service")
+    machine.wait_for_open_port(3551, "${ipAddr}")
+    res = machine.succeed("apcaccess")
+    expect_line="MBATTCHG : 42 Percent"
+    assert "MBATTCHG : 42 Percent" in res, f"expected apcaccess output to contain '{expect_line}' but got '{res}'"
+    machine.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/apfs.nix b/nixpkgs/nixos/tests/apfs.nix
new file mode 100644
index 000000000000..15ed5aa7f573
--- /dev/null
+++ b/nixpkgs/nixos/tests/apfs.nix
@@ -0,0 +1,65 @@
+{ lib, ... }: {
+  name = "apfs";
+  meta.maintainers = with lib.maintainers; [ Luflosi ];
+
+  nodes.machine = {
+    virtualisation.emptyDiskImages = [ 1024 ];
+
+    boot.supportedFilesystems = [ "apfs" ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("basic.target")
+    machine.succeed("mkdir /tmp/mnt")
+
+    with subtest("mkapfs refuses to work with a label that is too long"):
+      machine.fail( "mkapfs -L '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F' /dev/vdb")
+
+    with subtest("mkapfs works with the maximum label length"):
+      machine.succeed("mkapfs -L '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7' /dev/vdb")
+
+    with subtest("Enable case sensitivity and normalization sensitivity"):
+      machine.succeed(
+          "mkapfs -s -z /dev/vdb",
+          "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
+          "echo 'Hello World 1' > /tmp/mnt/test.txt",
+          "[ ! -f /tmp/mnt/TeSt.TxT ] || false", # Test case sensitivity
+          "echo 'Hello World 1' | diff - /tmp/mnt/test.txt",
+          "echo 'Hello World 2' > /tmp/mnt/\u0061\u0301.txt",
+          "echo 'Hello World 2' | diff - /tmp/mnt/\u0061\u0301.txt",
+          "[ ! -f /tmp/mnt/\u00e1.txt ] || false", # Test Unicode normalization sensitivity
+          "umount /tmp/mnt",
+          "apfsck /dev/vdb",
+      )
+    with subtest("Disable case sensitivity and normalization sensitivity"):
+      machine.succeed(
+          "mkapfs /dev/vdb",
+          "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
+          "echo 'bla bla bla' > /tmp/mnt/Test.txt",
+          "echo -n 'Hello World' > /tmp/mnt/test.txt",
+          "echo ' 1' >> /tmp/mnt/TEST.TXT",
+          "umount /tmp/mnt",
+          "apfsck /dev/vdb",
+          "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
+          "echo 'Hello World 1' | diff - /tmp/mnt/TeSt.TxT", # Test case insensitivity
+          "echo 'Hello World 2' > /tmp/mnt/\u0061\u0301.txt",
+          "echo 'Hello World 2' | diff - /tmp/mnt/\u0061\u0301.txt",
+          "echo 'Hello World 2' | diff - /tmp/mnt/\u00e1.txt", # Test Unicode normalization
+          "umount /tmp/mnt",
+          "apfsck /dev/vdb",
+      )
+    with subtest("Snapshots"):
+      machine.succeed(
+          "mkapfs /dev/vdb",
+          "mount -o cknodes,readwrite /dev/vdb /tmp/mnt",
+          "echo 'Hello World' > /tmp/mnt/test.txt",
+          "apfs-snap /tmp/mnt snap-1",
+          "rm /tmp/mnt/test.txt",
+          "umount /tmp/mnt",
+          "mount -o cknodes,readwrite,snap=snap-1 /dev/vdb /tmp/mnt",
+          "echo 'Hello World' | diff - /tmp/mnt/test.txt",
+          "umount /tmp/mnt",
+          "apfsck /dev/vdb",
+      )
+  '';
+}
diff --git a/nixpkgs/nixos/tests/apparmor.nix b/nixpkgs/nixos/tests/apparmor.nix
new file mode 100644
index 000000000000..be91e9632849
--- /dev/null
+++ b/nixpkgs/nixos/tests/apparmor.nix
@@ -0,0 +1,85 @@
+import ./make-test-python.nix ({ pkgs, lib, ... } : {
+  name = "apparmor";
+  meta.maintainers = with lib.maintainers; [ julm ];
+
+  nodes.machine =
+    { lib, pkgs, config, ... }:
+    {
+      security.apparmor.enable = lib.mkDefault true;
+    };
+
+  testScript =
+    ''
+      machine.wait_for_unit("multi-user.target")
+
+      with subtest("AppArmor profiles are loaded"):
+          machine.succeed("systemctl status apparmor.service")
+
+      # AppArmor securityfs
+      with subtest("AppArmor securityfs is mounted"):
+          machine.succeed("mountpoint -q /sys/kernel/security")
+          machine.succeed("cat /sys/kernel/security/apparmor/profiles")
+
+      # Test apparmorRulesFromClosure by:
+      # 1. Prepending a string of the relevant packages' name and version on each line.
+      # 2. Sorting according to those strings.
+      # 3. Removing those prepended strings.
+      # 4. Using `diff` against the expected output.
+      with subtest("apparmorRulesFromClosure"):
+          machine.succeed(
+              "${pkgs.diffutils}/bin/diff -u ${pkgs.writeText "expected.rules" ''
+                  mr ${pkgs.bash}/lib/**.so*,
+                  r ${pkgs.bash},
+                  r ${pkgs.bash}/etc/**,
+                  r ${pkgs.bash}/lib/**,
+                  r ${pkgs.bash}/share/**,
+                  x ${pkgs.bash}/foo/**,
+                  mr ${pkgs.glibc}/lib/**.so*,
+                  r ${pkgs.glibc},
+                  r ${pkgs.glibc}/etc/**,
+                  r ${pkgs.glibc}/lib/**,
+                  r ${pkgs.glibc}/share/**,
+                  x ${pkgs.glibc}/foo/**,
+                  mr ${pkgs.libcap}/lib/**.so*,
+                  r ${pkgs.libcap},
+                  r ${pkgs.libcap}/etc/**,
+                  r ${pkgs.libcap}/lib/**,
+                  r ${pkgs.libcap}/share/**,
+                  x ${pkgs.libcap}/foo/**,
+                  mr ${pkgs.libcap.lib}/lib/**.so*,
+                  r ${pkgs.libcap.lib},
+                  r ${pkgs.libcap.lib}/etc/**,
+                  r ${pkgs.libcap.lib}/lib/**,
+                  r ${pkgs.libcap.lib}/share/**,
+                  x ${pkgs.libcap.lib}/foo/**,
+                  mr ${pkgs.libidn2.out}/lib/**.so*,
+                  r ${pkgs.libidn2.out},
+                  r ${pkgs.libidn2.out}/etc/**,
+                  r ${pkgs.libidn2.out}/lib/**,
+                  r ${pkgs.libidn2.out}/share/**,
+                  x ${pkgs.libidn2.out}/foo/**,
+                  mr ${pkgs.libunistring}/lib/**.so*,
+                  r ${pkgs.libunistring},
+                  r ${pkgs.libunistring}/etc/**,
+                  r ${pkgs.libunistring}/lib/**,
+                  r ${pkgs.libunistring}/share/**,
+                  x ${pkgs.libunistring}/foo/**,
+                  mr ${pkgs.glibc.libgcc}/lib/**.so*,
+                  r ${pkgs.glibc.libgcc},
+                  r ${pkgs.glibc.libgcc}/etc/**,
+                  r ${pkgs.glibc.libgcc}/lib/**,
+                  r ${pkgs.glibc.libgcc}/share/**,
+                  x ${pkgs.glibc.libgcc}/foo/**,
+              ''} ${pkgs.runCommand "actual.rules" { preferLocalBuild = true; } ''
+                  ${pkgs.gnused}/bin/sed -e 's:^[^ ]* ${builtins.storeDir}/[^,/-]*-\([^/,]*\):\1 \0:' ${
+                      pkgs.apparmorRulesFromClosure {
+                        name = "ping";
+                        additionalRules = ["x $path/foo/**"];
+                      } [ pkgs.libcap ]
+                  } |
+                  ${pkgs.coreutils}/bin/sort -n -k1 |
+                  ${pkgs.gnused}/bin/sed -e 's:^[^ ]* ::' >$out
+              ''}"
+          )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/appliance-repart-image.nix b/nixpkgs/nixos/tests/appliance-repart-image.nix
new file mode 100644
index 000000000000..861369b9f3ca
--- /dev/null
+++ b/nixpkgs/nixos/tests/appliance-repart-image.nix
@@ -0,0 +1,111 @@
+# Tests building and running a GUID Partition Table (GPT) appliance image.
+# "Appliance" here means that the image does not contain the normal NixOS
+# infrastructure of a system profile and cannot be re-built via
+# `nixos-rebuild`.
+
+{ lib, ... }:
+
+let
+  rootPartitionLabel = "root";
+
+  imageId = "nixos-appliance";
+  imageVersion = "1-rc1";
+in
+{
+  name = "appliance-gpt-image";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+
+    imports = [ ../modules/image/repart.nix ];
+
+    virtualisation.directBoot.enable = false;
+    virtualisation.mountHostNixStore = false;
+    virtualisation.useEFIBoot = true;
+
+    # Disable boot loaders because we install one "manually".
+    # TODO(raitobezarius): revisit this when #244907 lands
+    boot.loader.grub.enable = false;
+
+    system.image.id = imageId;
+    system.image.version = imageVersion;
+
+    virtualisation.fileSystems = lib.mkForce {
+      "/" = {
+        device = "/dev/disk/by-partlabel/${rootPartitionLabel}";
+        fsType = "ext4";
+      };
+    };
+
+    image.repart = {
+      name = "appliance-gpt-image";
+      # OVMF does not work with the default repart sector size of 4096
+      sectorSize = 512;
+      partitions = {
+        "esp" = {
+          contents =
+            let
+              efiArch = config.nixpkgs.hostPlatform.efiArch;
+            in
+            {
+              "/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source =
+                "${pkgs.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi";
+
+              "/EFI/Linux/${config.system.boot.loader.ukiFile}".source =
+                "${config.system.build.uki}/${config.system.boot.loader.ukiFile}";
+            };
+          repartConfig = {
+            Type = "esp";
+            Format = "vfat";
+            # Minimize = "guess" seems to not work very vell for vfat
+            # partitons. It's better to set a sensible default instead. The
+            # aarch64 kernel seems to generally be a little bigger than the
+            # x86_64 kernel. To stay on the safe side, leave some more slack
+            # for every platform other than x86_64.
+            SizeMinBytes = if config.nixpkgs.hostPlatform.isx86_64 then "64M" else "96M";
+          };
+        };
+        "root" = {
+          storePaths = [ config.system.build.toplevel ];
+          repartConfig = {
+            Type = "root";
+            Format = config.fileSystems."/".fsType;
+            Label = rootPartitionLabel;
+            Minimize = "guess";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    import os
+    import subprocess
+    import tempfile
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    subprocess.run([
+      "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img",
+      "create",
+      "-f",
+      "qcow2",
+      "-b",
+      "${nodes.machine.system.build.image}/${nodes.machine.image.repart.imageFile}",
+      "-F",
+      "raw",
+      tmp_disk_image.name,
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+
+    os_release = machine.succeed("cat /etc/os-release")
+    assert 'IMAGE_ID="${imageId}"' in os_release
+    assert 'IMAGE_VERSION="${imageVersion}"' in os_release
+
+    bootctl_status = machine.succeed("bootctl status")
+    assert "Boot Loader Specification Type #2 (.efi)" in bootctl_status
+  '';
+}
diff --git a/nixpkgs/nixos/tests/archi.nix b/nixpkgs/nixos/tests/archi.nix
new file mode 100644
index 000000000000..59f2e940c005
--- /dev/null
+++ b/nixpkgs/nixos/tests/archi.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "archi";
+  meta.maintainers = with lib.maintainers; [ paumr ];
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    environment.systemPackages = with pkgs; [ archi ];
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    machine.wait_for_x()
+
+    with subtest("createEmptyModel via CLI"):
+         machine.succeed("Archi -application com.archimatetool.commandline.app -consoleLog -nosplash --createEmptyModel --saveModel smoke.archimate")
+         machine.copy_from_vm("smoke.archimate", "")
+
+    with subtest("UI smoketest"):
+         machine.succeed("DISPLAY=:0 Archi --createEmptyModel >&2 &")
+         machine.wait_for_window("Archi")
+
+         # wait till main UI is open
+         machine.wait_for_text("Welcome to Archi")
+
+         machine.screenshot("welcome-screen")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/atd.nix b/nixpkgs/nixos/tests/atd.nix
new file mode 100644
index 000000000000..4342e9d7dc18
--- /dev/null
+++ b/nixpkgs/nixos/tests/atd.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "atd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ bjornfor ];
+  };
+
+  nodes.machine =
+    { ... }:
+    { services.atd.enable = true;
+      users.users.alice = { isNormalUser = true; };
+    };
+
+  # "at" has a resolution of 1 minute
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("atd.service")  # wait for atd to start
+    machine.fail("test -f ~root/at-1")
+    machine.fail("test -f ~alice/at-1")
+
+    machine.succeed("echo 'touch ~root/at-1' | at now+1min")
+    machine.succeed("su - alice -c \"echo 'touch at-1' | at now+1min\"")
+
+    machine.succeed("sleep 1.5m")
+
+    machine.succeed("test -f ~root/at-1")
+    machine.succeed("test -f ~alice/at-1")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/atop.nix b/nixpkgs/nixos/tests/atop.nix
new file mode 100644
index 000000000000..f9335eecc20e
--- /dev/null
+++ b/nixpkgs/nixos/tests/atop.nix
@@ -0,0 +1,226 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let assertions = rec {
+  path = program: path: ''
+    with subtest("The path of ${program} should be ${path}"):
+        p = machine.succeed("type -p \"${program}\" | head -c -1")
+        assert p == "${path}", f"${program} is {p}, expected ${path}"
+  '';
+  unit = name: state: ''
+    with subtest("Unit ${name} should be ${state}"):
+        if "${state}" == "active":
+            machine.wait_for_unit("${name}")
+        else:
+            machine.require_unit_state("${name}", "${state}")
+  '';
+  version = ''
+    import re
+
+    with subtest("binary should report the correct version"):
+        pkgver = "${pkgs.atop.version}"
+        ver = re.sub(r'(?s)^Version: (\d\.\d\.\d).*', r'\1', machine.succeed("atop -V"))
+        assert ver == pkgver, f"Version is `{ver}`, expected `{pkgver}`"
+  '';
+  atoprc = contents:
+    if builtins.stringLength contents > 0 then ''
+      with subtest("/etc/atoprc should have the correct contents"):
+          f = machine.succeed("cat /etc/atoprc")
+          assert f == "${contents}", f"/etc/atoprc contents: '{f}', expected '${contents}'"
+    '' else ''
+      with subtest("/etc/atoprc should not be present"):
+          machine.succeed("test ! -e /etc/atoprc")
+    '';
+  wrapper = present:
+    if present then path "atop" "/run/wrappers/bin/atop" + ''
+      with subtest("Wrapper should be setuid root"):
+          stat = machine.succeed("stat --printf '%a %u' /run/wrappers/bin/atop")
+          assert stat == "4511 0", f"Wrapper stat is {stat}, expected '4511 0'"
+    ''
+    else path "atop" "/run/current-system/sw/bin/atop";
+  atopService = present:
+    if present then
+      unit "atop.service" "active"
+      + ''
+        with subtest("atop.service should write some data to /var/log/atop"):
+
+            def has_data_files(last: bool) -> bool:
+                files = int(machine.succeed("ls -1 /var/log/atop | wc -l"))
+                if files == 0:
+                    machine.log("Did not find at least one 1 data file")
+                    if not last:
+                        machine.log("Will retry...")
+                    return False
+                return True
+
+            with machine.nested("Waiting for data files"):
+                retry(has_data_files)
+      '' else unit "atop.service" "inactive";
+  atopRotateTimer = present:
+    unit "atop-rotate.timer" (if present then "active" else "inactive");
+  atopacctService = present:
+    if present then
+      unit "atopacct.service" "active"
+      + ''
+        with subtest("atopacct.service should enable process accounting"):
+            machine.wait_until_succeeds("test -f /run/pacct_source")
+
+        with subtest("atopacct.service should write data to /run/pacct_shadow.d"):
+
+            def has_data_files(last: bool) -> bool:
+                files = int(machine.succeed("ls -1 /run/pacct_shadow.d | wc -l"))
+                if files == 0:
+                    machine.log("Did not find at least one 1 data file")
+                    if not last:
+                        machine.log("Will retry...")
+                    return False
+                return True
+
+            with machine.nested("Waiting for data files"):
+                retry(has_data_files)
+      '' else unit "atopacct.service" "inactive";
+  netatop = present:
+    if present then
+      unit "netatop.service" "active"
+      + ''
+        with subtest("The netatop kernel module should be loaded"):
+            out = machine.succeed("modprobe -n -v netatop")
+            assert out == "", f"Module should be loaded already, but modprobe would have done {out}."
+      '' else ''
+      with subtest("The netatop kernel module should be absent"):
+          machine.fail("modprobe -n -v netatop")
+    '';
+  atopgpu = present:
+    if present then
+      (unit "atopgpu.service" "active") + (path "atopgpud" "/run/current-system/sw/bin/atopgpud")
+    else (unit "atopgpu.service" "inactive") + ''
+      with subtest("atopgpud should not be present"):
+          machine.fail("type -p atopgpud")
+    '';
+};
+in
+{
+  justThePackage = makeTest {
+    name = "atop-justThePackage";
+    nodes.machine = {
+      environment.systemPackages = [ pkgs.atop ];
+    };
+    testScript = with assertions; builtins.concatStringsSep "\n" [
+      version
+      (atoprc "")
+      (wrapper false)
+      (atopService false)
+      (atopRotateTimer false)
+      (atopacctService false)
+      (netatop false)
+      (atopgpu false)
+    ];
+  };
+  defaults = makeTest {
+    name = "atop-defaults";
+    nodes.machine = {
+      programs.atop = {
+        enable = true;
+      };
+    };
+    testScript = with assertions; builtins.concatStringsSep "\n" [
+      version
+      (atoprc "")
+      (wrapper false)
+      (atopService true)
+      (atopRotateTimer true)
+      (atopacctService true)
+      (netatop false)
+      (atopgpu false)
+    ];
+  };
+  minimal = makeTest {
+    name = "atop-minimal";
+    nodes.machine = {
+      programs.atop = {
+        enable = true;
+        atopService.enable = false;
+        atopRotateTimer.enable = false;
+        atopacctService.enable = false;
+      };
+    };
+    testScript = with assertions; builtins.concatStringsSep "\n" [
+      version
+      (atoprc "")
+      (wrapper false)
+      (atopService false)
+      (atopRotateTimer false)
+      (atopacctService false)
+      (netatop false)
+      (atopgpu false)
+    ];
+  };
+  netatop = makeTest {
+    name = "atop-netatop";
+    nodes.machine = {
+      programs.atop = {
+        enable = true;
+        netatop.enable = true;
+      };
+    };
+    testScript = with assertions; builtins.concatStringsSep "\n" [
+      version
+      (atoprc "")
+      (wrapper false)
+      (atopService true)
+      (atopRotateTimer true)
+      (atopacctService true)
+      (netatop true)
+      (atopgpu false)
+    ];
+  };
+  atopgpu = makeTest {
+    name = "atop-atopgpu";
+    nodes.machine = {
+      programs.atop = {
+        enable = true;
+        atopgpu.enable = true;
+      };
+    };
+    testScript = with assertions; builtins.concatStringsSep "\n" [
+      version
+      (atoprc "")
+      (wrapper false)
+      (atopService true)
+      (atopRotateTimer true)
+      (atopacctService true)
+      (netatop false)
+      (atopgpu true)
+    ];
+  };
+  everything = makeTest {
+    name = "atop-everything";
+    nodes.machine = {
+      programs.atop = {
+        enable = true;
+        settings = {
+          flags = "faf1";
+          interval = 2;
+        };
+        setuidWrapper.enable = true;
+        netatop.enable = true;
+        atopgpu.enable = true;
+      };
+    };
+    testScript = with assertions; builtins.concatStringsSep "\n" [
+      version
+      (atoprc "flags faf1\\ninterval 2\\n")
+      (wrapper true)
+      (atopService true)
+      (atopRotateTimer true)
+      (atopacctService true)
+      (netatop true)
+      (atopgpu true)
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/tests/atuin.nix b/nixpkgs/nixos/tests/atuin.nix
new file mode 100644
index 000000000000..3164c83c683d
--- /dev/null
+++ b/nixpkgs/nixos/tests/atuin.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  testPort = 8888;
+  testUser = "testerman";
+  testPass = "password";
+  testEmail = "test.testerman@test.com";
+in
+{
+  name = "atuin";
+  meta.maintainers = with lib.maintainers; [ devusb ];
+
+  nodes = {
+    server =
+      { ... }:
+      {
+        services.postgresql.enable = true;
+
+        services.atuin = {
+          enable = true;
+          port = testPort;
+          host = "0.0.0.0";
+          openFirewall = true;
+          openRegistration = true;
+        };
+      };
+
+    client =
+      { ... }:
+      { };
+
+  };
+
+  testScript = with pkgs; ''
+    start_all()
+
+    # wait for atuin server startup
+    server.wait_for_unit("atuin.service")
+    server.wait_for_open_port(${toString testPort})
+
+    # configure atuin client on server node
+    server.execute("mkdir -p ~/.config/atuin")
+    server.execute("echo 'sync_address = \"http://localhost:${toString testPort}\"' > ~/.config/atuin/config.toml")
+
+    # register with atuin server on server node
+    server.succeed("${atuin}/bin/atuin register -u ${testUser} -p ${testPass} -e ${testEmail}")
+    _, key = server.execute("${atuin}/bin/atuin key")
+
+    # store test record in atuin server and sync
+    server.succeed("ATUIN_SESSION=$(${atuin}/bin/atuin uuid) ${atuin}/bin/atuin history start 'shazbot'")
+    server.succeed("${atuin}/bin/atuin sync")
+
+    # configure atuin client on client node
+    client.execute("mkdir -p ~/.config/atuin")
+    client.execute("echo 'sync_address = \"http://server:${toString testPort}\"' > ~/.config/atuin/config.toml")
+
+    # log in to atuin server on client node
+    client.succeed(f"${atuin}/bin/atuin login -u ${testUser} -p ${testPass} -k \"{key}\"")
+
+    # pull records from atuin server
+    client.succeed("${atuin}/bin/atuin sync -f")
+
+    # check for test record
+    client.succeed("ATUIN_SESSION=$(${atuin}/bin/atuin uuid) ${atuin}/bin/atuin history list | grep shazbot")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/audiobookshelf.nix b/nixpkgs/nixos/tests/audiobookshelf.nix
new file mode 100644
index 000000000000..64bd415160ee
--- /dev/null
+++ b/nixpkgs/nixos/tests/audiobookshelf.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "audiobookshelf";
+  meta.maintainers = with maintainers; [ wietsedv ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.audiobookshelf = {
+        enable = true;
+        port = 1234;
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("audiobookshelf.service")
+    machine.wait_for_open_port(1234)
+    machine.succeed("curl --fail http://localhost:1234/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/auth-mysql.nix b/nixpkgs/nixos/tests/auth-mysql.nix
new file mode 100644
index 000000000000..77a69eb1cd58
--- /dev/null
+++ b/nixpkgs/nixos/tests/auth-mysql.nix
@@ -0,0 +1,178 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  dbUser = "nixos_auth";
+  dbPassword = "topsecret123";
+  dbName = "auth";
+
+  mysqlUsername = "mysqltest";
+  mysqlPassword = "topsecretmysqluserpassword123";
+  mysqlGroup = "mysqlusers";
+
+  localUsername = "localtest";
+  localPassword = "topsecretlocaluserpassword123";
+
+  mysqlInit = pkgs.writeText "mysqlInit" ''
+      CREATE USER '${dbUser}'@'localhost' IDENTIFIED BY '${dbPassword}';
+      CREATE DATABASE ${dbName};
+      GRANT ALL PRIVILEGES ON ${dbName}.* TO '${dbUser}'@'localhost';
+      FLUSH PRIVILEGES;
+
+      USE ${dbName};
+      CREATE TABLE `groups` (
+        rowid int(11) NOT NULL auto_increment,
+        gid int(11) NOT NULL,
+        name char(255) NOT NULL,
+        PRIMARY KEY (rowid)
+      );
+
+      CREATE TABLE `users` (
+        name varchar(255) NOT NULL,
+        uid int(11) NOT NULL auto_increment,
+        gid int(11) NOT NULL,
+        password varchar(255) NOT NULL,
+        PRIMARY KEY (uid),
+        UNIQUE (name)
+      ) AUTO_INCREMENT=5000;
+
+      INSERT INTO `users` (name, uid, gid, password) VALUES
+      ('${mysqlUsername}', 5000, 5000, SHA2('${mysqlPassword}', 256));
+      INSERT INTO `groups` (name, gid) VALUES ('${mysqlGroup}', 5000);
+    '';
+in
+{
+  name = "auth-mysql";
+  meta.maintainers = with lib.maintainers; [ netali ];
+
+  nodes.machine =
+    { ... }:
+    {
+      services.mysql = {
+        enable = true;
+        package = pkgs.mariadb;
+        settings.mysqld.bind-address = "127.0.0.1";
+        initialScript = mysqlInit;
+      };
+
+      users.users.${localUsername} = {
+        isNormalUser = true;
+        password = localPassword;
+      };
+
+      security.pam.services.login.makeHomeDir = true;
+
+      users.mysql = {
+        enable = true;
+        host = "127.0.0.1";
+        user = dbUser;
+        database = dbName;
+        passwordFile = "${builtins.toFile "dbPassword" dbPassword}";
+        pam = {
+          table = "users";
+          userColumn = "name";
+          passwordColumn = "password";
+          passwordCrypt = "sha256";
+          disconnectEveryOperation = true;
+        };
+        nss = {
+          getpwnam = ''
+            SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
+            FROM users \
+            WHERE name='%1$s' \
+            LIMIT 1
+          '';
+          getpwuid = ''
+            SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
+            FROM users \
+            WHERE uid=%1$u \
+            LIMIT 1
+          '';
+          getspnam = ''
+            SELECT name, password, 1, 0, 99999, 7, 0, -1, 0 \
+            FROM users \
+            WHERE name='%1$s' \
+            LIMIT 1
+          '';
+          getpwent = ''
+            SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
+            FROM users
+          '';
+          getspent = ''
+            SELECT name, password, 1, 0, 99999, 7, 0, -1, 0 \
+            FROM users
+          '';
+          getgrnam = ''
+            SELECT name, 'x', gid FROM groups WHERE name='%1$s' LIMIT 1
+          '';
+          getgrgid = ''
+            SELECT name, 'x', gid FROM groups WHERE gid='%1$u' LIMIT 1
+          '';
+          getgrent = ''
+            SELECT name, 'x', gid FROM groups
+          '';
+          memsbygid = ''
+            SELECT name FROM users WHERE gid=%1$u
+          '';
+          gidsbymem = ''
+            SELECT gid FROM users WHERE name='%1$s'
+          '';
+        };
+      };
+    };
+
+  testScript = ''
+    def switch_to_tty(tty_number):
+        machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'")
+        machine.send_key(f"alt-f{tty_number}")
+        machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]")
+        machine.wait_for_unit(f"getty@tty{tty_number}.service")
+        machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'")
+
+
+    def try_login(tty_number, username, password):
+        machine.wait_until_tty_matches(tty_number, "login: ")
+        machine.send_chars(f"{username}\n")
+        machine.wait_until_tty_matches(tty_number, f"login: {username}")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches(tty_number, "Password: ")
+        machine.send_chars(f"{password}\n")
+
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("mysql.service")
+    machine.wait_until_succeeds("cat /etc/security/pam_mysql.conf | grep users.db_passwd")
+    machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+    with subtest("Local login"):
+        switch_to_tty("2")
+        try_login("2", "${localUsername}", "${localPassword}")
+
+        machine.wait_until_succeeds("pgrep -u ${localUsername} bash")
+        machine.send_chars("id > local_id.txt\n")
+        machine.wait_for_file("/home/${localUsername}/local_id.txt")
+        machine.succeed("cat /home/${localUsername}/local_id.txt | grep 'uid=1000(${localUsername}) gid=100(users) groups=100(users)'")
+
+    with subtest("Local incorrect login"):
+        switch_to_tty("3")
+        try_login("3", "${localUsername}", "wrongpassword")
+
+        machine.wait_until_tty_matches("3", "Login incorrect")
+        machine.wait_until_tty_matches("3", "login:")
+
+    with subtest("MySQL login"):
+        switch_to_tty("4")
+        try_login("4", "${mysqlUsername}", "${mysqlPassword}")
+
+        machine.wait_until_succeeds("pgrep -u ${mysqlUsername} bash")
+        machine.send_chars("id > mysql_id.txt\n")
+        machine.wait_for_file("/home/${mysqlUsername}/mysql_id.txt")
+        machine.succeed("cat /home/${mysqlUsername}/mysql_id.txt | grep 'uid=5000(${mysqlUsername}) gid=5000(${mysqlGroup}) groups=5000(${mysqlGroup})'")
+
+    with subtest("MySQL incorrect login"):
+        switch_to_tty("5")
+        try_login("5", "${mysqlUsername}", "wrongpassword")
+
+        machine.wait_until_tty_matches("5", "Login incorrect")
+        machine.wait_until_tty_matches("5", "login:")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/authelia.nix b/nixpkgs/nixos/tests/authelia.nix
new file mode 100644
index 000000000000..679c65fea087
--- /dev/null
+++ b/nixpkgs/nixos/tests/authelia.nix
@@ -0,0 +1,169 @@
+# Test Authelia as an auth server for Traefik as a reverse proxy of a local web service
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "authelia";
+  meta.maintainers = with lib.maintainers; [ jk ];
+
+  nodes = {
+    authelia = { config, pkgs, lib, ... }: {
+      services.authelia.instances.testing = {
+        enable = true;
+        secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile";
+        secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile";
+        settings = {
+          authentication_backend.file.path = "/etc/authelia/users_database.yml";
+          access_control.default_policy = "one_factor";
+          session.domain = "example.com";
+          storage.local.path = "/tmp/db.sqlite3";
+          notifier.filesystem.filename = "/tmp/notifications.txt";
+        };
+      };
+
+      # These should not be set from nix but through other means to not leak the secret!
+      # This is purely for testing purposes!
+      environment.etc."authelia/storageEncryptionKeyFile" = {
+        mode = "0400";
+        user = "authelia-testing";
+        text = "you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this";
+      };
+      environment.etc."authelia/jwtSecretFile" = {
+        mode = "0400";
+        user = "authelia-testing";
+        text = "a_very_important_secret";
+      };
+      environment.etc."authelia/users_database.yml" = {
+        mode = "0400";
+        user = "authelia-testing";
+        text = ''
+          users:
+            bob:
+              disabled: false
+              displayname: bob
+              # password of password
+              password: $argon2id$v=19$m=65536,t=3,p=4$2ohUAfh9yetl+utr4tLcCQ$AsXx0VlwjvNnCsa70u4HKZvFkC8Gwajr2pHGKcND/xs
+              email: bob@jim.com
+              groups:
+                - admin
+                - dev
+        '';
+      };
+
+      services.traefik = {
+        enable = true;
+
+        dynamicConfigOptions = {
+          tls.certificates =
+            let
+              certDir = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+                openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=example.com/CN=auth.example.com/CN=static.example.com' -days 36500
+                mkdir -p $out
+                cp key.pem cert.pem $out
+              '';
+            in
+            [{
+              certFile = "${certDir}/cert.pem";
+              keyFile = "${certDir}/key.pem";
+            }];
+          http.middlewares.authelia.forwardAuth = {
+            address = "http://localhost:9091/api/verify?rd=https%3A%2F%2Fauth.example.com%2F";
+            trustForwardHeader = true;
+            authResponseHeaders = [
+              "Remote-User"
+              "Remote-Groups"
+              "Remote-Email"
+              "Remote-Name"
+            ];
+          };
+          http.middlewares.authelia-basic.forwardAuth = {
+            address = "http://localhost:9091/api/verify?auth=basic";
+            trustForwardHeader = true;
+            authResponseHeaders = [
+              "Remote-User"
+              "Remote-Groups"
+              "Remote-Email"
+              "Remote-Name"
+            ];
+          };
+
+          http.routers.simplehttp = {
+            rule = "Host(`static.example.com`)";
+            tls = true;
+            entryPoints = "web";
+            service = "simplehttp";
+          };
+          http.routers.simplehttp-basic-auth = {
+            rule = "Host(`static-basic-auth.example.com`)";
+            tls = true;
+            entryPoints = "web";
+            service = "simplehttp";
+            middlewares = [ "authelia-basic@file" ];
+          };
+
+          http.services.simplehttp = {
+            loadBalancer.servers = [{
+              url = "http://localhost:8000";
+            }];
+          };
+
+          http.routers.authelia = {
+            rule = "Host(`auth.example.com`)";
+            tls = true;
+            entryPoints = "web";
+            service = "authelia@file";
+          };
+
+          http.services.authelia = {
+            loadBalancer.servers = [{
+              url = "http://localhost:9091";
+            }];
+          };
+        };
+
+        staticConfigOptions = {
+          global = {
+            checkNewVersion = false;
+            sendAnonymousUsage = false;
+          };
+
+          entryPoints.web.address = ":443";
+        };
+      };
+
+      systemd.services.simplehttp =
+        let fakeWebPageDir = pkgs.writeTextDir "index.html" "hello"; in
+        {
+          script = "${pkgs.python3}/bin/python -m http.server --directory ${fakeWebPageDir} 8000";
+          serviceConfig.Type = "simple";
+          wantedBy = [ "multi-user.target" ];
+        };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    authelia.wait_for_unit("simplehttp.service")
+    authelia.wait_for_unit("traefik.service")
+    authelia.wait_for_unit("authelia-testing.service")
+    authelia.wait_for_open_port(443)
+    authelia.wait_for_unit("multi-user.target")
+
+    with subtest("Check for authelia"):
+      # expect the login page
+      assert "Login - Authelia", "could not reach authelia" in \
+        authelia.succeed("curl --insecure -sSf -H Host:auth.example.com https://authelia:443/")
+
+    with subtest("Check contacting basic http server via traefik with https works"):
+      assert "hello", "could not reach raw static site" in \
+        authelia.succeed("curl --insecure -sSf -H Host:static.example.com https://authelia:443/")
+
+    with subtest("Test traefik and authelia"):
+      with subtest("No details fail"):
+        authelia.fail("curl --insecure -sSf -H Host:static-basic-auth.example.com https://authelia:443/")
+      with subtest("Incorrect details fail"):
+        authelia.fail("curl --insecure -sSf -u 'bob:wordpass' -H Host:static-basic-auth.example.com https://authelia:443/")
+        authelia.fail("curl --insecure -sSf -u 'alice:password' -H Host:static-basic-auth.example.com https://authelia:443/")
+      with subtest("Correct details pass"):
+        assert "hello", "could not reach authed static site with valid credentials" in \
+          authelia.succeed("curl --insecure -sSf -u 'bob:password' -H Host:static-basic-auth.example.com https://authelia:443/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/avahi.nix b/nixpkgs/nixos/tests/avahi.nix
new file mode 100644
index 000000000000..d8f4d13340fb
--- /dev/null
+++ b/nixpkgs/nixos/tests/avahi.nix
@@ -0,0 +1,79 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+# bool: whether to use networkd in the tests
+, networkd ? false
+} @ args:
+
+# Test whether `avahi-daemon' and `libnss-mdns' work as expected.
+import ./make-test-python.nix {
+  name = "avahi";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes = let
+    cfg = { ... }: {
+      services.avahi = {
+        enable = true;
+        nssmdns4 = true;
+        publish.addresses = true;
+        publish.domain = true;
+        publish.enable = true;
+        publish.userServices = true;
+        publish.workstation = true;
+        extraServiceFiles.ssh = "${pkgs.avahi}/etc/avahi/services/ssh.service";
+      };
+    } // pkgs.lib.optionalAttrs (networkd) {
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+      };
+    };
+  in {
+    one = cfg;
+    two = cfg;
+  };
+
+  testScript = ''
+    start_all()
+
+    # mDNS.
+    one.wait_for_unit("network.target")
+    two.wait_for_unit("network.target")
+
+    one.succeed("avahi-resolve-host-name one.local | tee out >&2")
+    one.succeed('test "`cut -f1 < out`" = one.local')
+    one.succeed("avahi-resolve-host-name two.local | tee out >&2")
+    one.succeed('test "`cut -f1 < out`" = two.local')
+
+    two.succeed("avahi-resolve-host-name one.local | tee out >&2")
+    two.succeed('test "`cut -f1 < out`" = one.local')
+    two.succeed("avahi-resolve-host-name two.local | tee out >&2")
+    two.succeed('test "`cut -f1 < out`" = two.local')
+
+    # Basic DNS-SD.
+    one.succeed("avahi-browse -r -t _workstation._tcp | tee out >&2")
+    one.succeed("test `wc -l < out` -gt 0")
+    two.succeed("avahi-browse -r -t _workstation._tcp | tee out >&2")
+    two.succeed("test `wc -l < out` -gt 0")
+
+    # More DNS-SD.
+    one.execute('avahi-publish -s "This is a test" _test._tcp 123 one=1 >&2 &')
+    one.sleep(5)
+    two.succeed("avahi-browse -r -t _test._tcp | tee out >&2")
+    two.succeed("test `wc -l < out` -gt 0")
+
+    # NSS-mDNS.
+    one.succeed("getent hosts one.local >&2")
+    one.succeed("getent hosts two.local >&2")
+    two.succeed("getent hosts one.local >&2")
+    two.succeed("getent hosts two.local >&2")
+
+    # extra service definitions
+    one.succeed("avahi-browse -r -t _ssh._tcp | tee out >&2")
+    one.succeed("test `wc -l < out` -gt 0")
+    two.succeed("avahi-browse -r -t _ssh._tcp | tee out >&2")
+    two.succeed("test `wc -l < out` -gt 0")
+  '';
+} args
diff --git a/nixpkgs/nixos/tests/ayatana-indicators.nix b/nixpkgs/nixos/tests/ayatana-indicators.nix
new file mode 100644
index 000000000000..a7de640f9e37
--- /dev/null
+++ b/nixpkgs/nixos/tests/ayatana-indicators.nix
@@ -0,0 +1,111 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+  user = "alice";
+in {
+  name = "ayatana-indicators";
+
+  meta = {
+    maintainers = lib.teams.lomiri.members;
+  };
+
+  nodes.machine = { config, ... }: {
+    imports = [
+      ./common/auto.nix
+      ./common/user-account.nix
+    ];
+
+    test-support.displayManager.auto = {
+      enable = true;
+      inherit user;
+    };
+
+    services.xserver = {
+      enable = true;
+      desktopManager.mate.enable = true;
+      displayManager.defaultSession = lib.mkForce "mate";
+    };
+
+    services.ayatana-indicators = {
+      enable = true;
+      packages = with pkgs; [
+        ayatana-indicator-datetime
+        ayatana-indicator-messages
+        ayatana-indicator-session
+      ] ++ (with pkgs.lomiri; [
+        lomiri-indicator-network
+        telephony-service
+      ]);
+    };
+
+    # Setup needed by some indicators
+
+    services.accounts-daemon.enable = true; # messages
+
+    # Lomiri-ish setup for Lomiri indicators
+    # TODO move into a Lomiri module, once the package set is far enough for the DE to start
+
+    networking.networkmanager.enable = true; # lomiri-network-indicator
+    # TODO potentially urfkill for lomiri-network-indicator?
+
+    services.dbus.packages = with pkgs.lomiri; [
+      libusermetrics
+    ];
+
+    environment.systemPackages = with pkgs.lomiri; [
+      lomiri-schemas
+    ];
+
+    services.telepathy.enable = true;
+
+    users.users.usermetrics = {
+      group = "usermetrics";
+      home = "/var/lib/usermetrics";
+      createHome = true;
+      isSystemUser = true;
+    };
+
+    users.groups.usermetrics = { };
+  };
+
+  # TODO session indicator starts up in a semi-broken state, but works fine after a restart. maybe being started before graphical session is truly up & ready?
+  testScript = { nodes, ... }: let
+    runCommandOverServiceList = list: command:
+      lib.strings.concatMapStringsSep "\n" command list;
+
+    runCommandOverAyatanaIndicators = runCommandOverServiceList
+      (builtins.filter
+        (service: !(lib.strings.hasPrefix "lomiri" service || lib.strings.hasPrefix "telephony-service" service))
+        nodes.machine.systemd.user.targets."ayatana-indicators".wants);
+
+    runCommandOverAllIndicators = runCommandOverServiceList
+      nodes.machine.systemd.user.targets."ayatana-indicators".wants;
+  in ''
+    start_all()
+    machine.wait_for_x()
+
+    # Desktop environment should reach graphical-session.target
+    machine.wait_for_unit("graphical-session.target", "${user}")
+
+    # MATE relies on XDG autostart to bring up the indicators.
+    # Not sure *when* XDG autostart fires them up, and awaiting pgrep success seems to misbehave?
+    machine.sleep(10)
+
+    # Now check if all indicators were brought up successfully, and kill them for later
+  '' + (runCommandOverAyatanaIndicators (service: let serviceExec = builtins.replaceStrings [ "." ] [ "-" ] service; in ''
+    machine.succeed("pgrep -u ${user} -f ${serviceExec}")
+    machine.succeed("pkill -f ${serviceExec}")
+  '')) + ''
+
+    # Ayatana target is the preferred way of starting up indicators on SystemD session, the graphical session is responsible for starting this if it supports them.
+    # Mate currently doesn't do this, so start it manually for checking (https://github.com/mate-desktop/mate-indicator-applet/issues/63)
+    machine.systemctl("start ayatana-indicators.target", "${user}")
+    machine.wait_for_unit("ayatana-indicators.target", "${user}")
+
+    # Let all indicator services do their startups, potential post-launch crash & restart cycles so we can properly check for failures
+    # Not sure if there's a better way of awaiting this without false-positive potential
+    machine.sleep(10)
+
+    # Now check if all indicator services were brought up successfully
+  '' + runCommandOverAllIndicators (service: ''
+    machine.wait_for_unit("${service}", "${user}")
+  '');
+})
diff --git a/nixpkgs/nixos/tests/babeld.nix b/nixpkgs/nixos/tests/babeld.nix
new file mode 100644
index 000000000000..e497aa5b64e1
--- /dev/null
+++ b/nixpkgs/nixos/tests/babeld.nix
@@ -0,0 +1,138 @@
+
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "babeld";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hexa ];
+  };
+
+  nodes =
+    { client = { pkgs, lib, ... }:
+      {
+        virtualisation.vlans = [ 10 ];
+
+        networking = {
+          useDHCP = false;
+          interfaces."eth1" = {
+            ipv4.addresses = lib.mkForce [ { address = "192.168.10.2"; prefixLength = 24; } ];
+            ipv4.routes = lib.mkForce [ { address = "0.0.0.0"; prefixLength = 0; via = "192.168.10.1"; } ];
+            ipv6.addresses = lib.mkForce [ { address = "2001:db8:10::2"; prefixLength = 64; } ];
+            ipv6.routes = lib.mkForce [ { address = "::"; prefixLength = 0; via = "2001:db8:10::1"; } ];
+          };
+        };
+      };
+
+      local_router = { pkgs, lib, ... }:
+      {
+        virtualisation.vlans = [ 10 20 ];
+
+        networking = {
+          useDHCP = false;
+          firewall.enable = false;
+
+          interfaces."eth1" = {
+            ipv4.addresses = lib.mkForce [ { address = "192.168.10.1"; prefixLength = 24; } ];
+            ipv6.addresses = lib.mkForce [ { address = "2001:db8:10::1"; prefixLength = 64; } ];
+          };
+
+          interfaces."eth2" = {
+            ipv4.addresses = lib.mkForce [ { address = "192.168.20.1"; prefixLength = 24; } ];
+            ipv6.addresses = lib.mkForce [ { address = "2001:db8:20::1"; prefixLength = 64; } ];
+          };
+        };
+
+        services.babeld = {
+          enable = true;
+          interfaces.eth2 = {
+            hello-interval = 1;
+            type = "wired";
+          };
+          extraConfig = ''
+            local-port-readwrite 33123
+
+            import-table 254 # main
+            export-table 254 # main
+
+            in ip 192.168.10.0/24 deny
+            in ip 192.168.20.0/24 deny
+            in ip 2001:db8:10::/64 deny
+            in ip 2001:db8:20::/64 deny
+
+            in ip 192.168.30.0/24 allow
+            in ip 2001:db8:30::/64 allow
+
+            in deny
+
+            redistribute local proto 2
+            redistribute local deny
+          '';
+        };
+      };
+      remote_router = { pkgs, lib, ... }:
+      {
+        virtualisation.vlans = [ 20 30 ];
+
+        networking = {
+          useDHCP = false;
+          firewall.enable = false;
+
+          interfaces."eth1" = {
+            ipv4.addresses = lib.mkForce [ { address = "192.168.20.2"; prefixLength = 24; } ];
+            ipv6.addresses = lib.mkForce [ { address = "2001:db8:20::2"; prefixLength = 64; } ];
+          };
+
+          interfaces."eth2" = {
+            ipv4.addresses = lib.mkForce [ { address = "192.168.30.1"; prefixLength = 24; } ];
+            ipv6.addresses = lib.mkForce [ { address = "2001:db8:30::1"; prefixLength = 64; } ];
+          };
+        };
+
+        services.babeld = {
+          enable = true;
+          interfaces.eth1 = {
+            hello-interval = 1;
+            type = "wired";
+          };
+          extraConfig = ''
+            local-port-readwrite 33123
+
+            import-table 254 # main
+            export-table 254 # main
+
+            in ip 192.168.20.0/24 deny
+            in ip 192.168.30.0/24 deny
+            in ip 2001:db8:20::/64 deny
+            in ip 2001:db8:30::/64 deny
+
+            in ip 192.168.10.0/24 allow
+            in ip 2001:db8:10::/64 allow
+
+            in deny
+
+            redistribute local proto 2
+            redistribute local deny
+          '';
+        };
+
+      };
+    };
+
+  testScript =
+    ''
+      start_all()
+
+      local_router.wait_for_unit("babeld.service")
+      remote_router.wait_for_unit("babeld.service")
+
+      local_router.wait_until_succeeds("ip route get 192.168.30.1")
+      local_router.wait_until_succeeds("ip route get 2001:db8:30::1")
+
+      remote_router.wait_until_succeeds("ip route get 192.168.10.1")
+      remote_router.wait_until_succeeds("ip route get 2001:db8:10::1")
+
+      client.succeed("ping -c1 192.168.30.1")
+      client.succeed("ping -c1 2001:db8:30::1")
+
+      remote_router.succeed("ping -c1 192.168.10.2")
+      remote_router.succeed("ping -c1 2001:db8:10::2")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/bazarr.nix b/nixpkgs/nixos/tests/bazarr.nix
new file mode 100644
index 000000000000..aa0550e243ae
--- /dev/null
+++ b/nixpkgs/nixos/tests/bazarr.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+let
+  port = 42069;
+in
+{
+  name = "bazarr";
+  meta.maintainers = with lib.maintainers; [ d-xo ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.bazarr = {
+        enable = true;
+        listenPort = port;
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("bazarr.service")
+    machine.wait_for_open_port(${toString port})
+    machine.succeed("curl --fail http://localhost:${toString port}/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bcachefs.nix b/nixpkgs/nixos/tests/bcachefs.nix
new file mode 100644
index 000000000000..ec3c2427f386
--- /dev/null
+++ b/nixpkgs/nixos/tests/bcachefs.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "bcachefs";
+  meta.maintainers = with pkgs.lib.maintainers; [ Madouura ];
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.emptyDiskImages = [ 4096 ];
+    networking.hostId = "deadbeef";
+    boot.supportedFilesystems = [ "bcachefs" ];
+    environment.systemPackages = with pkgs; [ parted keyutils ];
+  };
+
+  testScript = ''
+    machine.succeed("modprobe bcachefs")
+    machine.succeed("bcachefs version")
+    machine.succeed("ls /dev")
+
+    machine.succeed(
+        "mkdir /tmp/mnt",
+        "udevadm settle",
+        "parted --script /dev/vdb mklabel msdos",
+        "parted --script /dev/vdb -- mkpart primary 1024M 50% mkpart primary 50% -1s",
+        "udevadm settle",
+        "echo password | bcachefs format --encrypted --metadata_replicas 2 --label vtest /dev/vdb1 /dev/vdb2",
+        "echo password | bcachefs unlock -k session /dev/vdb1",
+        "echo password | mount -t bcachefs /dev/vdb1:/dev/vdb2 /tmp/mnt",
+        "udevadm settle",
+        "bcachefs fs usage /tmp/mnt",
+        "umount /tmp/mnt",
+        "udevadm settle",
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/beanstalkd.nix b/nixpkgs/nixos/tests/beanstalkd.nix
new file mode 100644
index 000000000000..518f018408ad
--- /dev/null
+++ b/nixpkgs/nixos/tests/beanstalkd.nix
@@ -0,0 +1,49 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  pythonEnv = pkgs.python3.withPackages (p: [p.beanstalkc]);
+
+  produce = pkgs.writeScript "produce.py" ''
+    #!${pythonEnv.interpreter}
+    import beanstalkc
+
+    queue = beanstalkc.Connection(host='localhost', port=11300, parse_yaml=False);
+    queue.put(b'this is a job')
+    queue.put(b'this is another job')
+  '';
+
+  consume = pkgs.writeScript "consume.py" ''
+    #!${pythonEnv.interpreter}
+    import beanstalkc
+
+    queue = beanstalkc.Connection(host='localhost', port=11300, parse_yaml=False);
+
+    job = queue.reserve(timeout=0)
+    print(job.body.decode('utf-8'))
+    job.delete()
+  '';
+
+in
+{
+  name = "beanstalkd";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  nodes.machine =
+    { ... }:
+    { services.beanstalkd.enable = true;
+    };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("beanstalkd.service")
+
+    machine.succeed("${produce}")
+    assert "this is a job\n" == machine.succeed(
+        "${consume}"
+    )
+    assert "this is another job\n" == machine.succeed(
+        "${consume}"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bees.nix b/nixpkgs/nixos/tests/bees.nix
new file mode 100644
index 000000000000..3ab9f38ada8f
--- /dev/null
+++ b/nixpkgs/nixos/tests/bees.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "bees";
+
+  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
+    '';
+    virtualisation.emptyDiskImages = [ 4096 4096 ];
+    virtualisation.fileSystems = {
+      "/aux1" = { # filesystem configured to be deduplicated
+        device = "/dev/disk/by-label/aux1";
+        fsType = "btrfs";
+      };
+      "/aux2" = { # filesystem not configured to be deduplicated
+        device = "/dev/disk/by-label/aux2";
+        fsType = "btrfs";
+      };
+    };
+    services.beesd.filesystems = {
+      aux1 = {
+        spec = "LABEL=aux1";
+        hashTableSizeMB = 16;
+        verbosity = "debug";
+      };
+    };
+  };
+
+  testScript =
+  let
+    someContentIsShared = loc: pkgs.writeShellScript "some-content-is-shared" ''
+      [[ $(btrfs fi du -s --raw ${lib.escapeShellArg loc}/dedup-me-{1,2} | awk 'BEGIN { count=0; } NR>1 && $3 == 0 { count++ } END { print count }') -eq 0 ]]
+    '';
+  in ''
+    # shut down the instance started by systemd at boot, so we can test our test procedure
+    machine.succeed("systemctl stop beesd@aux1.service")
+
+    machine.succeed(
+        "dd if=/dev/urandom of=/aux1/dedup-me-1 bs=1M count=8",
+        "cp --reflink=never /aux1/dedup-me-1 /aux1/dedup-me-2",
+        "cp --reflink=never /aux1/* /aux2/",
+        "sync",
+    )
+    machine.fail(
+        "${someContentIsShared "/aux1"}",
+        "${someContentIsShared "/aux2"}",
+    )
+    machine.succeed("systemctl start beesd@aux1.service")
+
+    # assert that "Set Shared" column is nonzero
+    machine.wait_until_succeeds(
+        "${someContentIsShared "/aux1"}",
+    )
+    machine.fail("${someContentIsShared "/aux2"}")
+
+    # assert that 16MB hash table size requested was honored
+    machine.succeed(
+        "[[ $(stat -c %s /aux1/.beeshome/beeshash.dat) = $(( 16 * 1024 * 1024)) ]]"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/binary-cache.nix b/nixpkgs/nixos/tests/binary-cache.nix
new file mode 100644
index 000000000000..bc1c6fb9a267
--- /dev/null
+++ b/nixpkgs/nixos/tests/binary-cache.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "binary-cache";
+  meta.maintainers = with lib.maintainers; [ thomasjm ];
+
+  nodes.machine =
+    { pkgs, ... }: {
+      imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      environment.systemPackages = [ pkgs.python3 ];
+      system.extraDependencies = [ pkgs.hello.inputDerivation ];
+      nix.extraOptions = ''
+        experimental-features = nix-command
+      '';
+    };
+
+  testScript = ''
+    # Build the cache, then remove it from the store
+    cachePath = machine.succeed("nix-build --no-out-link -E 'with import <nixpkgs> {}; mkBinaryCache { rootPaths = [hello]; }'").strip()
+    machine.succeed("cp -r %s/. /tmp/cache" % cachePath)
+    machine.succeed("nix-store --delete " + cachePath)
+
+    # Sanity test of cache structure
+    status, stdout = machine.execute("ls /tmp/cache")
+    cache_files = stdout.split()
+    assert ("nix-cache-info" in cache_files)
+    assert ("nar" in cache_files)
+
+    # Nix store ping should work
+    machine.succeed("nix store ping --store file:///tmp/cache")
+
+    # Cache should contain a .narinfo referring to "hello"
+    grepLogs = machine.succeed("grep -l 'StorePath: /nix/store/[[:alnum:]]*-hello-.*' /tmp/cache/*.narinfo")
+
+    # Get the store path referenced by the .narinfo
+    narInfoFile = grepLogs.strip()
+    narInfoContents = machine.succeed("cat " + narInfoFile)
+    import re
+    match = re.match(r"^StorePath: (/nix/store/[a-z0-9]*-hello-.*)$", narInfoContents, re.MULTILINE)
+    if not match: raise Exception("Couldn't find hello store path in cache")
+    storePath = match[1]
+
+    # Delete the store path
+    machine.succeed("nix-store --delete " + storePath)
+    machine.succeed("[ ! -d %s ] || exit 1" % storePath)
+
+    # Should be able to build hello using the cache
+    logs = machine.succeed("nix-build -A hello '<nixpkgs>' --option require-sigs false --option trusted-substituters file:///tmp/cache --option substituters file:///tmp/cache 2>&1")
+    logLines = logs.split("\n")
+    if not "this path will be fetched" in logLines[0]: raise Exception("Unexpected first log line")
+    def shouldBe(got, desired):
+      if got != desired: raise Exception("Expected '%s' but got '%s'" % (desired, got))
+    shouldBe(logLines[1], "  " + storePath)
+    shouldBe(logLines[2], "copying path '%s' from 'file:///tmp/cache'..." % storePath)
+    shouldBe(logLines[3], storePath)
+
+    # Store path should exist in the store now
+    machine.succeed("[ -d %s ] || exit 1" % storePath)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bind.nix b/nixpkgs/nixos/tests/bind.nix
new file mode 100644
index 000000000000..15accbd49db4
--- /dev/null
+++ b/nixpkgs/nixos/tests/bind.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix {
+  name = "bind";
+
+  nodes.machine = { pkgs, lib, ... }: {
+    services.bind.enable = true;
+    services.bind.extraOptions = "empty-zones-enable no;";
+    services.bind.zones = lib.singleton {
+      name = ".";
+      master = true;
+      file = pkgs.writeText "root.zone" ''
+        $TTL 3600
+        . IN SOA ns.example.org. admin.example.org. ( 1 3h 1h 1w 1d )
+        . IN NS ns.example.org.
+
+        ns.example.org. IN A    192.168.0.1
+        ns.example.org. IN AAAA abcd::1
+
+        1.0.168.192.in-addr.arpa IN PTR ns.example.org.
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("bind.service")
+    machine.wait_for_open_port(53)
+    machine.succeed("host 192.168.0.1 127.0.0.1 | grep -qF ns.example.org")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/bird.nix b/nixpkgs/nixos/tests/bird.nix
new file mode 100644
index 000000000000..822a7caea9ba
--- /dev/null
+++ b/nixpkgs/nixos/tests/bird.nix
@@ -0,0 +1,129 @@
+# This test does a basic functionality check for all bird variants and demonstrates a use
+# of the preCheckConfig option.
+
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) optionalString;
+
+  makeBird2Host = hostId: { pkgs, ... }: {
+    virtualisation.vlans = [ 1 ];
+
+    environment.systemPackages = with pkgs; [ jq ];
+
+    networking = {
+      useNetworkd = true;
+      useDHCP = false;
+      firewall.enable = false;
+    };
+
+    systemd.network.networks."01-eth1" = {
+      name = "eth1";
+      networkConfig.Address = "10.0.0.${hostId}/24";
+    };
+
+    services.bird2 = {
+      enable = true;
+
+      config = ''
+        log syslog all;
+
+        debug protocols all;
+
+        router id 10.0.0.${hostId};
+
+        protocol device {
+        }
+
+        protocol kernel kernel4 {
+          ipv4 {
+            import none;
+            export all;
+          };
+        }
+
+        protocol static static4 {
+          ipv4;
+          include "static4.conf";
+        }
+
+        protocol ospf v2 ospf4 {
+          ipv4 {
+            export all;
+          };
+          area 0 {
+            interface "eth1" {
+              hello 5;
+              wait 5;
+            };
+          };
+        }
+
+        protocol kernel kernel6 {
+          ipv6 {
+            import none;
+            export all;
+          };
+        }
+
+        protocol static static6 {
+          ipv6;
+          include "static6.conf";
+        }
+
+        protocol ospf v3 ospf6 {
+          ipv6 {
+            export all;
+          };
+          area 0 {
+            interface "eth1" {
+              hello 5;
+              wait 5;
+            };
+          };
+        }
+      '';
+
+      preCheckConfig = ''
+        echo "route 1.2.3.4/32 blackhole;" > static4.conf
+        echo "route fd00::/128 blackhole;" > static6.conf
+      '';
+    };
+
+    systemd.tmpfiles.rules = [
+      "f /etc/bird/static4.conf - - - - route 10.10.0.${hostId}/32 blackhole;"
+      "f /etc/bird/static6.conf - - - - route fdff::${hostId}/128 blackhole;"
+    ];
+  };
+in
+makeTest {
+  name = "bird2";
+
+  nodes.host1 = makeBird2Host "1";
+  nodes.host2 = makeBird2Host "2";
+
+  testScript = ''
+    start_all()
+
+    host1.wait_for_unit("bird2.service")
+    host2.wait_for_unit("bird2.service")
+    host1.succeed("systemctl reload bird2.service")
+
+    with subtest("Waiting for advertised IPv4 routes"):
+      host1.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.2\")) | any'")
+      host2.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.1\")) | any'")
+    with subtest("Waiting for advertised IPv6 routes"):
+      host1.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::2\")) | any'")
+      host2.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::1\")) | any'")
+
+    with subtest("Check fake routes in preCheckConfig do not exists"):
+      host1.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")
+      host2.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")
+
+      host1.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")
+      host2.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/birdwatcher.nix b/nixpkgs/nixos/tests/birdwatcher.nix
new file mode 100644
index 000000000000..5c41b4d0e4f3
--- /dev/null
+++ b/nixpkgs/nixos/tests/birdwatcher.nix
@@ -0,0 +1,94 @@
+# This test does a basic functionality check for birdwatcher
+
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) optionalString;
+in
+makeTest {
+  name = "birdwatcher";
+  nodes = {
+    host1 = {
+      environment.systemPackages = with pkgs; [ jq ];
+      services.bird2 = {
+        enable = true;
+        config = ''
+          log syslog all;
+
+          debug protocols all;
+
+          router id 10.0.0.1;
+
+          protocol device {
+          }
+
+          protocol kernel kernel4 {
+            ipv4 {
+              import none;
+              export all;
+            };
+          }
+
+          protocol kernel kernel6 {
+            ipv6 {
+              import none;
+              export all;
+            };
+          }
+        '';
+      };
+      services.birdwatcher = {
+        enable = true;
+        settings = ''
+          [server]
+          allow_from = []
+          allow_uncached = false
+          modules_enabled = ["status",
+                             "protocols",
+                             "protocols_bgp",
+                             "protocols_short",
+                             "routes_protocol",
+                             "routes_peer",
+                             "routes_table",
+                             "routes_table_filtered",
+                             "routes_table_peer",
+                             "routes_filtered",
+                             "routes_prefixed",
+                             "routes_noexport",
+                             "routes_pipe_filtered_count",
+                             "routes_pipe_filtered"
+                            ]
+          [status]
+          reconfig_timestamp_source = "bird"
+          reconfig_timestamp_match = "# created: (.*)"
+          filter_fields = []
+          [bird]
+          listen = "0.0.0.0:29184"
+          config = "/etc/bird/bird2.conf"
+          birdc  = "${pkgs.bird}/bin/birdc"
+          ttl = 5 # time to live (in minutes) for caching of cli output
+          [parser]
+          filter_fields = []
+          [cache]
+          use_redis = false # if not using redis cache, activate housekeeping to save memory!
+          [housekeeping]
+          interval = 5
+          force_release_memory = true
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    host1.wait_for_unit("bird2.service")
+    host1.wait_for_unit("birdwatcher.service")
+    host1.wait_for_open_port(29184)
+    host1.succeed("curl http://[::]:29184/status | jq -r .status.message | grep 'Daemon is up and running'")
+    host1.succeed("curl http://[::]:29184/protocols | jq -r .protocols.device1.state | grep 'up'")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/bitcoind.nix b/nixpkgs/nixos/tests/bitcoind.nix
new file mode 100644
index 000000000000..7726a23d853e
--- /dev/null
+++ b/nixpkgs/nixos/tests/bitcoind.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "bitcoind";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
+
+  nodes.machine = { ... }: {
+    services.bitcoind."mainnet" = {
+      enable = true;
+      rpc = {
+        port = 8332;
+        users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
+        users.rpc2.passwordHMAC = "1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225";
+      };
+    };
+
+    environment.etc."test.blank".text = "";
+    services.bitcoind."testnet" = {
+      enable = true;
+      configFile = "/etc/test.blank";
+      testnet = true;
+      rpc = {
+        port = 18332;
+      };
+      extraCmdlineOptions = [ "-rpcuser=rpc" "-rpcpassword=rpc" "-rpcauth=rpc2:1495e4a3ad108187576c68f7f9b5ddc5$accce0881c74aa01bb8960ff3bdbd39f607fd33178147679e055a4ac35f53225" ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("bitcoind-mainnet.service")
+    machine.wait_for_unit("bitcoind-testnet.service")
+
+    machine.wait_until_succeeds(
+        'curl --fail --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 |  grep \'"chain":"main"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --fail --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:8332 |  grep \'"chain":"main"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --fail --user rpc:rpc --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 |  grep \'"chain":"test"\' '
+    )
+    machine.wait_until_succeeds(
+        'curl --fail --user rpc2:rpc2 --data-binary \'{"jsonrpc": "1.0", "id":"curltest", "method": "getblockchaininfo", "params": [] }\' -H \'content-type: text/plain;\' localhost:18332 |  grep \'"chain":"test"\' '
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bittorrent.nix b/nixpkgs/nixos/tests/bittorrent.nix
new file mode 100644
index 000000000000..473b05d4c98e
--- /dev/null
+++ b/nixpkgs/nixos/tests/bittorrent.nix
@@ -0,0 +1,168 @@
+# This test runs a Bittorrent tracker on one machine, and verifies
+# that two client machines can download the torrent using
+# `transmission'.  The first client (behind a NAT router) downloads
+# from the initial seeder running on the tracker.  Then we kill the
+# initial seeder.  The second client downloads from the first client,
+# which only works if the first client successfully uses the UPnP-IGD
+# protocol to poke a hole in the NAT.
+
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+
+  # Some random file to serve.
+  file = pkgs.hello.src;
+
+  internalRouterAddress = "192.168.3.1";
+  internalClient1Address = "192.168.3.2";
+  externalRouterAddress = "80.100.100.1";
+  externalClient2Address = "80.100.100.2";
+  externalTrackerAddress = "80.100.100.3";
+
+  download-dir = "/var/lib/transmission/Downloads";
+  transmissionConfig = { ... }: {
+    environment.systemPackages = [ pkgs.transmission ];
+    services.transmission = {
+      enable = true;
+      settings = {
+        dht-enabled = false;
+        message-level = 2;
+        inherit download-dir;
+      };
+    };
+  };
+in
+
+{
+  name = "bittorrent";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ domenkozar eelco rob bobvanderlinden ];
+  };
+
+  nodes = {
+    tracker = { pkgs, ... }: {
+      imports = [ transmissionConfig ];
+
+      virtualisation.vlans = [ 1 ];
+      networking.firewall.enable = false;
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = externalTrackerAddress; prefixLength = 24; }
+      ];
+
+      # We need Apache on the tracker to serve the torrents.
+      services.httpd = {
+        enable = true;
+        virtualHosts = {
+          "torrentserver.org" = {
+            adminAddr = "foo@example.org";
+            documentRoot = "/tmp";
+          };
+        };
+      };
+      services.opentracker.enable = true;
+    };
+
+    router = { pkgs, nodes, ... }: {
+      virtualisation.vlans = [ 1 2 ];
+      networking.nat.enable = true;
+      networking.nat.internalInterfaces = [ "eth2" ];
+      networking.nat.externalInterface = "eth1";
+      networking.firewall.enable = true;
+      networking.firewall.trustedInterfaces = [ "eth2" ];
+      networking.interfaces.eth0.ipv4.addresses = [];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = externalRouterAddress; prefixLength = 24; }
+      ];
+      networking.interfaces.eth2.ipv4.addresses = [
+        { address = internalRouterAddress; prefixLength = 24; }
+      ];
+      services.miniupnpd = {
+        enable = true;
+        externalInterface = "eth1";
+        internalIPs = [ "eth2" ];
+        appendConfig = ''
+          ext_ip=${externalRouterAddress}
+        '';
+      };
+    };
+
+    client1 = { pkgs, nodes, ... }: {
+      imports = [ transmissionConfig ];
+      environment.systemPackages = [ pkgs.miniupnpc ];
+
+      virtualisation.vlans = [ 2 ];
+      networking.interfaces.eth0.ipv4.addresses = [];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = internalClient1Address; prefixLength = 24; }
+      ];
+      networking.defaultGateway = internalRouterAddress;
+      networking.firewall.enable = false;
+    };
+
+    client2 = { pkgs, ... }: {
+      imports = [ transmissionConfig ];
+
+      virtualisation.vlans = [ 1 ];
+      networking.interfaces.eth0.ipv4.addresses = [];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = externalClient2Address; prefixLength = 24; }
+      ];
+      networking.firewall.enable = false;
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+      start_all()
+
+      # Wait for network and miniupnpd.
+      router.systemctl("start network-online.target")
+      router.wait_for_unit("network-online.target")
+      router.wait_for_unit("miniupnpd")
+
+      # Create the torrent.
+      tracker.succeed("mkdir ${download-dir}/data")
+      tracker.succeed(
+          "cp ${file} ${download-dir}/data/test.tar.bz2"
+      )
+      tracker.succeed(
+          "transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
+      )
+      tracker.succeed("chmod 644 /tmp/test.torrent")
+
+      # Start the tracker.  !!! use a less crappy tracker
+      tracker.systemctl("start network-online.target")
+      tracker.wait_for_unit("network-online.target")
+      tracker.wait_for_unit("opentracker.service")
+      tracker.wait_for_open_port(6969)
+
+      # Start the initial seeder.
+      tracker.succeed(
+          "transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
+      )
+
+      # Now we should be able to download from the client behind the NAT.
+      tracker.wait_for_unit("httpd")
+      client1.systemctl("start network-online.target")
+      client1.wait_for_unit("network-online.target")
+      client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
+      client1.wait_for_file("${download-dir}/test.tar.bz2")
+      client1.succeed(
+          "cmp ${download-dir}/test.tar.bz2 ${file}"
+      )
+
+      # Bring down the initial seeder.
+      tracker.stop_job("transmission")
+
+      # Now download from the second client.  This can only succeed if
+      # the first client created a NAT hole in the router.
+      client2.systemctl("start network-online.target")
+      client2.wait_for_unit("network-online.target")
+      client2.succeed(
+          "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
+      )
+      client2.wait_for_file("${download-dir}/test.tar.bz2")
+      client2.succeed(
+          "cmp ${download-dir}/test.tar.bz2 ${file}"
+      )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/blockbook-frontend.nix b/nixpkgs/nixos/tests/blockbook-frontend.nix
new file mode 100644
index 000000000000..dca4f2f53cc1
--- /dev/null
+++ b/nixpkgs/nixos/tests/blockbook-frontend.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "blockbook-frontend";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
+
+  nodes.machine = { ... }: {
+    services.blockbook-frontend."test" = {
+      enable = true;
+    };
+    services.bitcoind.mainnet = {
+      enable = true;
+      rpc = {
+        port = 8030;
+        users.rpc.passwordHMAC = "acc2374e5f9ba9e62a5204d3686616cf$53abdba5e67a9005be6a27ca03a93ce09e58854bc2b871523a0d239a72968033";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("blockbook-frontend-test.service")
+
+    machine.wait_for_open_port(9030)
+
+    machine.succeed("curl -sSfL http://localhost:9030 | grep 'Blockbook'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/blocky.nix b/nixpkgs/nixos/tests/blocky.nix
new file mode 100644
index 000000000000..18e7f45e1c73
--- /dev/null
+++ b/nixpkgs/nixos/tests/blocky.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix {
+  name = "blocky";
+
+  nodes = {
+    server = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.dnsutils ];
+      services.blocky = {
+        enable = true;
+
+        settings = {
+          customDNS = {
+            mapping = {
+              "printer.lan" = "192.168.178.3,2001:0db8:85a3:08d3:1319:8a2e:0370:7344";
+            };
+          };
+          upstream = {
+            default = [ "8.8.8.8" "1.1.1.1" ];
+          };
+          port = 53;
+          httpPort = 5000;
+          logLevel = "info";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    with subtest("Service test"):
+        server.wait_for_unit("blocky.service")
+        server.wait_for_open_port(53)
+        server.wait_for_open_port(5000)
+        server.succeed("dig @127.0.0.1 +short -x 192.168.178.3 | grep -qF printer.lan")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/boot-stage1.nix b/nixpkgs/nixos/tests/boot-stage1.nix
new file mode 100644
index 000000000000..f07802b8c31e
--- /dev/null
+++ b/nixpkgs/nixos/tests/boot-stage1.nix
@@ -0,0 +1,164 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "boot-stage1";
+
+  nodes.machine = { config, pkgs, lib, ... }: {
+    boot.extraModulePackages = let
+      compileKernelModule = name: source: pkgs.runCommandCC name rec {
+        inherit source;
+        kdev = config.boot.kernelPackages.kernel.dev;
+        kver = config.boot.kernelPackages.kernel.modDirVersion;
+        ksrc = "${kdev}/lib/modules/${kver}/build";
+        hardeningDisable = [ "pic" ];
+        nativeBuildInputs = kdev.moduleBuildDependencies;
+      } ''
+        echo "obj-m += $name.o" > Makefile
+        echo "$source" > "$name.c"
+        make -C "$ksrc" M=$(pwd) modules
+        install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko"
+      '';
+
+      # This spawns a kthread which just waits until it gets a signal and
+      # terminates if that is the case. We want to make sure that nothing during
+      # the boot process kills any kthread by accident, like what happened in
+      # issue #15226.
+      kcanary = compileKernelModule "kcanary" ''
+        #include <linux/version.h>
+        #include <linux/init.h>
+        #include <linux/module.h>
+        #include <linux/kernel.h>
+        #include <linux/kthread.h>
+        #include <linux/sched.h>
+        #include <linux/signal.h>
+        #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
+        #include <linux/sched/signal.h>
+        #endif
+
+        MODULE_LICENSE("GPL");
+
+        struct task_struct *canaryTask;
+
+        static int kcanary(void *nothing)
+        {
+          allow_signal(SIGINT);
+          allow_signal(SIGTERM);
+          allow_signal(SIGKILL);
+          while (!kthread_should_stop()) {
+            set_current_state(TASK_INTERRUPTIBLE);
+            schedule_timeout_interruptible(msecs_to_jiffies(100));
+            if (signal_pending(current)) break;
+          }
+          return 0;
+        }
+
+        static int kcanaryInit(void)
+        {
+          kthread_run(&kcanary, NULL, "kcanary");
+          return 0;
+        }
+
+        static void kcanaryExit(void)
+        {
+          kthread_stop(canaryTask);
+        }
+
+        module_init(kcanaryInit);
+        module_exit(kcanaryExit);
+      '';
+
+    in lib.singleton kcanary;
+
+    boot.initrd.kernelModules = [ "kcanary" ];
+
+    boot.initrd.extraUtilsCommands = let
+      compile = name: source: pkgs.runCommandCC name { inherit source; } ''
+        mkdir -p "$out/bin"
+        echo "$source" | gcc -Wall -o "$out/bin/$name" -xc -
+      '';
+
+      daemonize = name: source: compile name ''
+        #include <stdio.h>
+        #include <unistd.h>
+
+        void runSource(void) {
+        ${source}
+        }
+
+        int main(void) {
+          if (fork() > 0) return 0;
+          setsid();
+          runSource();
+          return 1;
+        }
+      '';
+
+      mkCmdlineCanary = { name, cmdline ? "", source ? "" }: (daemonize name ''
+        char *argv[] = {"${cmdline}", NULL};
+        execvp("${name}-child", argv);
+      '') // {
+        child = compile "${name}-child" ''
+          #include <stdio.h>
+          #include <unistd.h>
+
+          int main(void) {
+            ${source}
+            while (1) sleep(1);
+            return 1;
+          }
+        '';
+      };
+
+      copyCanaries = lib.concatMapStrings (canary: ''
+        ${lib.optionalString (canary ? child) ''
+          copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
+        ''}
+        copy_bin_and_libs "${canary}/bin/${canary.name}"
+      '');
+
+    in copyCanaries [
+      # Simple canary process which just sleeps forever and should be killed by
+      # stage 2.
+      (daemonize "canary1" "while (1) sleep(1);")
+
+      # We want this canary process to try mimicking a kthread using a cmdline
+      # with a zero length so we can make sure that the process is properly
+      # killed in stage 1.
+      (mkCmdlineCanary {
+        name = "canary2";
+        source = ''
+          FILE *f;
+          f = fopen("/run/canary2.pid", "w");
+          fprintf(f, "%d\n", getpid());
+          fclose(f);
+        '';
+      })
+
+      # This canary process mimics a storage daemon, which we do NOT want to be
+      # killed before going into stage 2. For more on root storage daemons, see:
+      # https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
+      (mkCmdlineCanary {
+        name = "canary3";
+        cmdline = "@canary3";
+      })
+    ];
+
+    boot.initrd.postMountCommands = ''
+      canary1
+      canary2
+      canary3
+      # Make sure the pidfile of canary 2 is created so that we still can get
+      # its former pid after the killing spree starts next within stage 1.
+      while [ ! -s /run/canary2.pid ]; do sleep 0.1; done
+    '';
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("test -s /run/canary2.pid")
+    machine.fail("pgrep -a canary1")
+    machine.fail("kill -0 $(< /run/canary2.pid)")
+    machine.succeed('pgrep -a -f "^@canary3$"')
+    machine.succeed('pgrep -a -f "^kcanary$"')
+  '';
+
+  meta.maintainers = with pkgs.lib.maintainers; [ aszlig ];
+})
diff --git a/nixpkgs/nixos/tests/boot.nix b/nixpkgs/nixos/tests/boot.nix
new file mode 100644
index 000000000000..ec2a9f6527c9
--- /dev/null
+++ b/nixpkgs/nixos/tests/boot.nix
@@ -0,0 +1,148 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
+
+  iso =
+    (import ../lib/eval-config.nix {
+      inherit system;
+      modules = [
+        ../modules/installer/cd-dvd/installation-cd-minimal.nix
+        ../modules/testing/test-instrumentation.nix
+      ];
+    }).config.system.build.isoImage;
+
+  sd =
+    (import ../lib/eval-config.nix {
+      inherit system;
+      modules = [
+        ../modules/installer/sd-card/sd-image-x86_64.nix
+        ../modules/testing/test-instrumentation.nix
+        { sdImage.compressImage = false; }
+      ];
+    }).config.system.build.sdImage;
+
+  pythonDict = params: "\n    {\n        ${concatStringsSep ",\n        " (mapAttrsToList (name: param: "\"${name}\": \"${param}\"") params)},\n    }\n";
+
+  makeBootTest = name: extraConfig:
+    let
+      machineConfig = pythonDict ({
+        qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
+        qemuFlags = "-m 768";
+      } // extraConfig);
+    in
+      makeTest {
+        name = "boot-" + name;
+        nodes = { };
+        testScript =
+          ''
+            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")
+
+            with subtest("Check whether the channel got installed correctly"):
+                machine.succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello")
+                machine.succeed("nix-env --dry-run -iA nixos.procps")
+
+            machine.shutdown()
+          '';
+      };
+
+  makeNetbootTest = name: extraConfig:
+    let
+      config = (import ../lib/eval-config.nix {
+          inherit system;
+          modules =
+            [ ../modules/installer/netboot/netboot.nix
+              ../modules/testing/test-instrumentation.nix
+              { key = "serial"; }
+            ];
+        }).config;
+      ipxeBootDir = pkgs.symlinkJoin {
+        name = "ipxeBootDir";
+        paths = [
+          config.system.build.netbootRamdisk
+          config.system.build.kernel
+          config.system.build.netbootIpxeScript
+        ];
+      };
+      machineConfig = pythonDict ({
+        qemuBinary = qemu-common.qemuBinary pkgs.qemu_test;
+        qemuFlags = "-boot order=n -m 2000";
+        netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe";
+      } // extraConfig);
+    in
+      makeTest {
+        name = "boot-netboot-" + name;
+        nodes = { };
+        testScript = ''
+            machine = create_machine(${machineConfig})
+            machine.start()
+            machine.wait_for_unit("multi-user.target")
+            machine.shutdown()
+          '';
+      };
+  uefiBinary = {
+    x86_64-linux = "${pkgs.OVMF.fd}/FV/OVMF.fd";
+    aarch64-linux = "${pkgs.OVMF.fd}/FV/QEMU_EFI.fd";
+  }.${pkgs.stdenv.hostPlatform.system};
+in {
+    uefiCdrom = makeBootTest "uefi-cdrom" {
+      cdrom = "${iso}/iso/${iso.isoName}";
+      bios = uefiBinary;
+    };
+
+    uefiUsb = makeBootTest "uefi-usb" {
+      usb = "${iso}/iso/${iso.isoName}";
+      bios = uefiBinary;
+    };
+
+    uefiNetboot = makeNetbootTest "uefi" {
+      bios = uefiBinary;
+      # Custom ROM is needed for EFI PXE boot. I failed to understand exactly why, because QEMU should still use iPXE for EFI.
+      netFrontendArgs = "romfile=${pkgs.ipxe}/ipxe.efirom";
+    };
+} // optionalAttrs (pkgs.stdenv.hostPlatform.system == "x86_64-linux") {
+    biosCdrom = makeBootTest "bios-cdrom" {
+      cdrom = "${iso}/iso/${iso.isoName}";
+    };
+
+    biosUsb = makeBootTest "bios-usb" {
+      usb = "${iso}/iso/${iso.isoName}";
+    };
+
+    biosNetboot = makeNetbootTest "bios" {};
+
+    ubootExtlinux = let
+      sdImage = "${sd}/sd-image/${sd.imageName}";
+      mutableImage = "/tmp/linked-image.qcow2";
+
+      machineConfig = pythonDict {
+        bios = "${pkgs.ubootQemuX86}/u-boot.rom";
+        qemuFlags = "-m 768 -machine type=pc,accel=tcg -drive file=${mutableImage},if=ide,format=qcow2";
+      };
+    in makeTest {
+      name = "boot-uboot-extlinux";
+      nodes = { };
+      testScript = ''
+        import os
+
+        # Create a mutable linked image backed by the read-only SD image
+        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})
+        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")
+        machine.shutdown()
+      '';
+    };
+}
diff --git a/nixpkgs/nixos/tests/bootspec.nix b/nixpkgs/nixos/tests/bootspec.nix
new file mode 100644
index 000000000000..14928b220625
--- /dev/null
+++ b/nixpkgs/nixos/tests/bootspec.nix
@@ -0,0 +1,201 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  baseline = {
+    virtualisation.useBootLoader = true;
+  };
+  grub = {
+    boot.loader.grub.enable = true;
+  };
+  systemd-boot = {
+    boot.loader.systemd-boot.enable = true;
+  };
+  uefi = {
+    virtualisation.useEFIBoot = true;
+    boot.loader.efi.canTouchEfiVariables = true;
+    boot.loader.grub.efiSupport = true;
+    environment.systemPackages = [ pkgs.efibootmgr ];
+  };
+  standard = {
+    boot.bootspec.enable = true;
+
+    imports = [
+      baseline
+      systemd-boot
+      uefi
+    ];
+  };
+in
+{
+  basic = makeTest {
+    name = "systemd-boot-with-bootspec";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = standard;
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+    '';
+  };
+
+  grub = makeTest {
+    name = "grub-with-bootspec";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      boot.bootspec.enable = true;
+
+      imports = [
+        baseline
+        grub
+        uefi
+      ];
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+    '';
+  };
+
+  legacy-boot = makeTest {
+    name = "legacy-boot-with-bootspec";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      boot.bootspec.enable = true;
+
+      imports = [
+        baseline
+        grub
+      ];
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+    '';
+  };
+
+  # Check that initrd create corresponding entries in bootspec.
+  initrd = makeTest {
+    name = "bootspec-with-initrd";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      imports = [ standard ];
+      environment.systemPackages = [ pkgs.jq ];
+      # It's probably the case, but we want to make it explicit here.
+      boot.initrd.enable = true;
+    };
+
+    testScript = ''
+      import json
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+
+      bootspec = json.loads(machine.succeed("jq -r '.\"org.nixos.bootspec.v1\"' /run/current-system/boot.json"))
+
+      assert 'initrd' in bootspec, "Bootspec should contain initrd field when initrd is enabled"
+      assert 'initrdSecrets' not in bootspec, "Bootspec should not contain initrdSecrets when there's no initrdSecrets"
+    '';
+  };
+
+  # Check that initrd secrets create corresponding entries in bootspec.
+  initrd-secrets = makeTest {
+    name = "bootspec-with-initrd-secrets";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      imports = [ standard ];
+      environment.systemPackages = [ pkgs.jq ];
+      # It's probably the case, but we want to make it explicit here.
+      boot.initrd.enable = true;
+      boot.initrd.secrets."/some/example" = pkgs.writeText "example-secret" "test";
+    };
+
+    testScript = ''
+      import json
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+
+      bootspec = json.loads(machine.succeed("jq -r '.\"org.nixos.bootspec.v1\"' /run/current-system/boot.json"))
+
+      assert 'initrdSecrets' in bootspec, "Bootspec should contain an 'initrdSecrets' field given there's an initrd secret"
+    '';
+  };
+
+
+  # Check that specialisations create corresponding entries in bootspec.
+  specialisation = makeTest {
+    name = "bootspec-with-specialisation";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = {
+      imports = [ standard ];
+      environment.systemPackages = [ pkgs.jq ];
+      specialisation.something.configuration = {};
+    };
+
+    testScript = ''
+      import json
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+      machine.succeed("test -e /run/current-system/specialisation/something/boot.json")
+
+      sp_in_parent = json.loads(machine.succeed("jq -r '.\"org.nixos.specialisation.v1\".something' /run/current-system/boot.json"))
+      sp_in_fs = json.loads(machine.succeed("cat /run/current-system/specialisation/something/boot.json"))
+
+      assert sp_in_parent['org.nixos.bootspec.v1'] == sp_in_fs['org.nixos.bootspec.v1'], "Bootspecs of the same specialisation are different!"
+    '';
+  };
+
+  # Check that extensions are propagated.
+  extensions = makeTest {
+    name = "bootspec-with-extensions";
+    meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+    nodes.machine = { config, ... }: {
+      imports = [ standard ];
+      environment.systemPackages = [ pkgs.jq ];
+      boot.bootspec.extensions = {
+        "org.nix-tests.product" = {
+          osRelease = config.environment.etc."os-release".source;
+        };
+      };
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      current_os_release = machine.succeed("cat /etc/os-release")
+      bootspec_os_release = machine.succeed("cat $(jq -r '.\"org.nix-tests.product\".osRelease' /run/current-system/boot.json)")
+
+      assert current_os_release == bootspec_os_release, "Filename referenced by extension has unexpected contents"
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/tests/borgbackup.nix b/nixpkgs/nixos/tests/borgbackup.nix
new file mode 100644
index 000000000000..4160e727f047
--- /dev/null
+++ b/nixpkgs/nixos/tests/borgbackup.nix
@@ -0,0 +1,230 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  passphrase = "supersecret";
+  dataDir = "/ran:dom/data";
+  excludeFile = "not_this_file";
+  keepFile = "important_file";
+  keepFileData = "important_data";
+  localRepo = "/root/back:up";
+  archiveName = "my_archive";
+  remoteRepo = "borg@server:."; # No need to specify path
+  privateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
+    RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
+    AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
+    9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+  publicKey = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
+  '';
+  privateKeyAppendOnly = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8
+    cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw
+    AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8
+    IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+  publicKeyAppendOnly = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
+  '';
+
+in {
+  name = "borgbackup";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [ dotlambda ];
+  };
+
+  nodes = {
+    client = { ... }: {
+      services.borgbackup.jobs = {
+
+        local = {
+          paths = dataDir;
+          repo = localRepo;
+          preHook = ''
+            # Don't append a timestamp
+            archiveName="${archiveName}"
+          '';
+          encryption = {
+            mode = "repokey";
+            inherit passphrase;
+          };
+          compression = "auto,zlib,9";
+          prune.keep = {
+            within = "1y";
+            yearly = 5;
+          };
+          exclude = [ "*/${excludeFile}" ];
+          postHook = "echo post";
+          startAt = [ ]; # Do not run automatically
+        };
+
+        remote = {
+          paths = dataDir;
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
+        remoteAppendOnly = {
+          paths = dataDir;
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
+        };
+
+        commandSuccess = {
+          dumpCommand = pkgs.writeScript "commandSuccess" ''
+            echo -n test
+          '';
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
+        commandFail = {
+          dumpCommand = "${pkgs.coreutils}/bin/false";
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
+        sleepInhibited = {
+          inhibitsSleep = true;
+          # Blocks indefinitely while "backing up" so that we can try to suspend the local system while it's hung
+          dumpCommand = pkgs.writeScript "sleepInhibited" ''
+            cat /dev/zero
+          '';
+          repo = remoteRepo;
+          encryption.mode = "none";
+          startAt = [ ];
+          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
+        };
+
+      };
+    };
+
+    server = { ... }: {
+      services.openssh = {
+        enable = true;
+        settings = {
+          PasswordAuthentication = false;
+          KbdInteractiveAuthentication = false;
+        };
+      };
+
+      services.borgbackup.repos.repo1 = {
+        authorizedKeys = [ publicKey ];
+        path = "/data/borgbackup";
+      };
+
+      # Second repo to make sure the authorizedKeys options are merged correctly
+      services.borgbackup.repos.repo2 = {
+        authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
+        path = "/data/borgbackup";
+        quota = ".5G";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    client.fail('test -d "${remoteRepo}"')
+
+    client.succeed(
+        "cp ${privateKey} /root/id_ed25519"
+    )
+    client.succeed("chmod 0600 /root/id_ed25519")
+    client.succeed(
+        "cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly"
+    )
+    client.succeed("chmod 0600 /root/id_ed25519.appendOnly")
+
+    client.succeed("mkdir -p ${dataDir}")
+    client.succeed("touch ${dataDir}/${excludeFile}")
+    client.succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}")
+
+    with subtest("local"):
+        borg = "BORG_PASSPHRASE='${passphrase}' borg"
+        client.systemctl("start --wait borgbackup-job-local")
+        client.fail("systemctl is-failed borgbackup-job-local")
+        # Make sure exactly one archive has been created
+        assert int(client.succeed("{} list '${localRepo}' | wc -l".format(borg))) > 0
+        # Make sure excludeFile has been excluded
+        client.fail(
+            "{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg)
+        )
+        # Make sure keepFile has the correct content
+        client.succeed("{} extract '${localRepo}::${archiveName}'".format(borg))
+        assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}")
+        # Make sure the same is true when using `borg mount`
+        client.succeed(
+            "mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format(
+                borg
+            )
+        )
+        assert "${keepFileData}" in client.succeed(
+            "cat /mnt/borg/${dataDir}/${keepFile}"
+        )
+
+    with subtest("remote"):
+        borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg"
+        server.wait_for_unit("sshd.service")
+        client.wait_for_unit("network.target")
+        client.systemctl("start --wait borgbackup-job-remote")
+        client.fail("systemctl is-failed borgbackup-job-remote")
+
+        # Make sure we can't access repos other than the specified one
+        client.fail("{} list borg\@server:wrong".format(borg))
+
+        # TODO: Make sure that data is actually deleted
+
+    with subtest("remoteAppendOnly"):
+        borg = (
+            "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg"
+        )
+        server.wait_for_unit("sshd.service")
+        client.wait_for_unit("network.target")
+        client.systemctl("start --wait borgbackup-job-remoteAppendOnly")
+        client.fail("systemctl is-failed borgbackup-job-remoteAppendOnly")
+
+        # Make sure we can't access repos other than the specified one
+        client.fail("{} list borg\@server:wrong".format(borg))
+
+        # TODO: Make sure that data is not actually deleted
+
+    with subtest("commandSuccess"):
+        server.wait_for_unit("sshd.service")
+        client.wait_for_unit("network.target")
+        client.systemctl("start --wait borgbackup-job-commandSuccess")
+        client.fail("systemctl is-failed borgbackup-job-commandSuccess")
+        id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip()
+        client.succeed(f"borg-job-commandSuccess extract ::{id} stdin")
+        assert "test" == client.succeed("cat stdin")
+
+    with subtest("commandFail"):
+        server.wait_for_unit("sshd.service")
+        client.wait_for_unit("network.target")
+        client.systemctl("start --wait borgbackup-job-commandFail")
+        client.succeed("systemctl is-failed borgbackup-job-commandFail")
+
+    with subtest("sleepInhibited"):
+        server.wait_for_unit("sshd.service")
+        client.wait_for_unit("network.target")
+        client.fail("systemd-inhibit --list | grep -q borgbackup")
+        client.systemctl("start borgbackup-job-sleepInhibited")
+        client.wait_until_succeeds("systemd-inhibit --list | grep -q borgbackup")
+        client.systemctl("stop borgbackup-job-sleepInhibited")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/botamusique.nix b/nixpkgs/nixos/tests/botamusique.nix
new file mode 100644
index 000000000000..ecb79cb69867
--- /dev/null
+++ b/nixpkgs/nixos/tests/botamusique.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+  name = "botamusique";
+  meta.maintainers = with lib.maintainers; [ hexa ];
+
+  nodes = {
+    machine = { config, ... }: {
+      networking.extraHosts = ''
+        127.0.0.1 all.api.radio-browser.info
+      '';
+
+      services.murmur = {
+        enable = true;
+        registerName = "NixOS tests";
+      };
+
+      services.botamusique = {
+        enable = true;
+        settings = {
+          server = {
+            channel = "NixOS tests";
+          };
+          bot = {
+            version = false;
+            auto_check_update = false;
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("murmur.service")
+    machine.wait_for_unit("botamusique.service")
+
+    machine.sleep(10)
+
+    machine.wait_until_succeeds(
+        "journalctl -u murmur.service -e | grep -q '<1:botamusique(-1)> Authenticated'"
+    )
+
+    with subtest("Check systemd hardening"):
+        output = machine.execute("systemctl show botamusique.service")[1]
+        machine.log(output)
+        output = machine.execute("systemd-analyze security botamusique.service")[1]
+        machine.log(output)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bpf.nix b/nixpkgs/nixos/tests/bpf.nix
new file mode 100644
index 000000000000..150ed0958862
--- /dev/null
+++ b/nixpkgs/nixos/tests/bpf.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "bpf";
+  meta.maintainers = with pkgs.lib.maintainers; [ martinetd ];
+
+  nodes.machine = { pkgs, ... }: {
+    programs.bcc.enable = true;
+    environment.systemPackages = with pkgs; [ bpftrace ];
+  };
+
+  testScript = ''
+    ## bcc
+    # syscount -d 1 stops 1s after probe started so is good for that
+    print(machine.succeed("syscount -d 1"))
+
+    ## bpftrace
+    # list probes
+    machine.succeed("bpftrace -l")
+    # simple BEGIN probe (user probe on bpftrace itself)
+    print(machine.succeed("bpftrace -e 'BEGIN { print(\"ok\"); exit(); }'"))
+    # tracepoint
+    print(machine.succeed("bpftrace -e 'tracepoint:syscalls:sys_enter_* { print(probe); exit() }'"))
+    # kprobe
+    print(machine.succeed("bpftrace -e 'kprobe:schedule { print(probe); exit() }'"))
+    # BTF
+    print(machine.succeed("bpftrace -e 'kprobe:schedule { "
+        "    printf(\"tgid: %d\", ((struct task_struct*) curtask)->tgid); exit() "
+        "}'"))
+    # module BTF (bpftrace >= 0.17)
+    # test is currently disabled on aarch64 as kfunc does not work there yet
+    # https://github.com/iovisor/bpftrace/issues/2496
+    print(machine.succeed("uname -m | grep aarch64 || "
+        "bpftrace -e 'kfunc:nft_trans_alloc_gfp { "
+        "    printf(\"portid: %d\\n\", args->ctx->portid); "
+        "} BEGIN { exit() }'"))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/bpftune.nix b/nixpkgs/nixos/tests/bpftune.nix
new file mode 100644
index 000000000000..c17bbcd11092
--- /dev/null
+++ b/nixpkgs/nixos/tests/bpftune.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+
+  name = "bpftune";
+
+  meta = {
+    maintainers = with lib.maintainers; [ nickcao ];
+  };
+
+  nodes = {
+    machine = { pkgs, ... }: {
+      services.bpftune.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("bpftune.service")
+    machine.wait_for_console_text("bpftune works")
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/breitbandmessung.nix b/nixpkgs/nixos/tests/breitbandmessung.nix
new file mode 100644
index 000000000000..78df0d5017eb
--- /dev/null
+++ b/nixpkgs/nixos/tests/breitbandmessung.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "breitbandmessung";
+  meta.maintainers = with lib.maintainers; [ b4dm4n ];
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [
+      ./common/user-account.nix
+      ./common/x11.nix
+    ];
+
+    # increase screen size to make the whole program visible
+    virtualisation.resolution = { x = 1280; y = 1024; };
+
+    test-support.displayManager.auto.user = "alice";
+
+    environment.systemPackages = with pkgs; [ breitbandmessung ];
+    environment.variables.XAUTHORITY = "/home/alice/.Xauthority";
+
+    # breitbandmessung is unfree
+    nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "breitbandmessung" ];
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    machine.wait_for_x()
+    machine.execute("su - alice -c breitbandmessung >&2  &")
+    machine.wait_for_window("Breitbandmessung")
+    machine.wait_for_text("Breitbandmessung")
+    machine.wait_for_text("Datenschutz")
+    machine.screenshot("breitbandmessung")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/brscan5.nix b/nixpkgs/nixos/tests/brscan5.nix
new file mode 100644
index 000000000000..9156a4cccfcf
--- /dev/null
+++ b/nixpkgs/nixos/tests/brscan5.nix
@@ -0,0 +1,43 @@
+# integration tests for brscan5 sane driver
+#
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "brscan5";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mattchrist ];
+  };
+
+  nodes.machine = { pkgs, ... }:
+    {
+      nixpkgs.config.allowUnfree = true;
+      hardware.sane = {
+        enable = true;
+        brscan5 = {
+          enable = true;
+          netDevices = {
+            "a" = { model="ADS-1200"; nodename="BRW0080927AFBCE"; };
+            "b" = { model="ADS-1200"; ip="192.168.1.2"; };
+          };
+        };
+      };
+    };
+
+  testScript = ''
+    import re
+    # sane loads libsane-brother5.so.1 successfully, and scanimage doesn't die
+    strace = machine.succeed('strace scanimage -L 2>&1').split("\n")
+    regexp = 'openat\(.*libsane-brother5.so.1", O_RDONLY|O_CLOEXEC\) = \d\d*$'
+    assert len([x for x in strace if re.match(regexp,x)]) > 0
+
+    # module creates a config
+    cfg = machine.succeed('cat /etc/opt/brother/scanner/brscan5/brsanenetdevice.cfg')
+    assert 'DEVICE=a , "ADS-1200" , 0x4f9:0x459 , NODENAME=BRW0080927AFBCE' in cfg
+    assert 'DEVICE=b , "ADS-1200" , 0x4f9:0x459 , IP-ADDRESS=192.168.1.2' in cfg
+
+    # scanimage lists the two network scanners
+    scanimage = machine.succeed("scanimage -L")
+    print(scanimage)
+    assert """device `brother5:net1;dev0' is a Brother b ADS-1200""" in scanimage
+    assert """device `brother5:net1;dev1' is a Brother a ADS-1200""" in scanimage
+  '';
+})
diff --git a/nixpkgs/nixos/tests/btrbk-doas.nix b/nixpkgs/nixos/tests/btrbk-doas.nix
new file mode 100644
index 000000000000..1e3f8d56addb
--- /dev/null
+++ b/nixpkgs/nixos/tests/btrbk-doas.nix
@@ -0,0 +1,114 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  let
+    privateKey = ''
+      -----BEGIN OPENSSH PRIVATE KEY-----
+      b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+      QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
+      RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
+      AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
+      9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
+      -----END OPENSSH PRIVATE KEY-----
+    '';
+    publicKey = ''
+      ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
+    '';
+  in
+  {
+    name = "btrbk-doas";
+    meta = with pkgs.lib; {
+      maintainers = with maintainers; [ symphorien tu-maurice ];
+    };
+
+    nodes = {
+      archive = { ... }: {
+        security.sudo.enable = false;
+        security.doas.enable = true;
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        # note: this makes the privateKey world readable.
+        # don't do it with real ssh keys.
+        environment.etc."btrbk_key".text = privateKey;
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          instances = {
+            remote = {
+              onCalendar = "minutely";
+              settings = {
+                ssh_identity = "/etc/btrbk_key";
+                ssh_user = "btrbk";
+                stream_compress = "lz4";
+                volume = {
+                  "ssh://main/mnt" = {
+                    target = "/mnt";
+                    snapshot_dir = "btrbk/remote";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+
+      main = { ... }: {
+        security.sudo.enable = false;
+        security.doas.enable = true;
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        services.openssh = {
+          enable = true;
+          passwordAuthentication = false;
+          kbdInteractiveAuthentication = false;
+        };
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          sshAccess = [
+            {
+              key = publicKey;
+              roles = [ "source" "send" "info" "delete" ];
+            }
+          ];
+          instances = {
+            local = {
+              onCalendar = "minutely";
+              settings = {
+                volume = {
+                  "/mnt" = {
+                    snapshot_dir = "btrbk/local";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # create btrfs partition at /mnt
+      for machine in (archive, main):
+        machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
+        machine.succeed("mkfs.btrfs /data_fs")
+        machine.succeed("mkdir /mnt")
+        machine.succeed("mount /data_fs /mnt")
+
+      # what to backup and where
+      main.succeed("btrfs subvolume create /mnt/to_backup")
+      main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
+
+      # check that local snapshots work
+      with subtest("local"):
+          main.succeed("echo foo > /mnt/to_backup/bar")
+          main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
+          main.succeed("echo bar > /mnt/to_backup/bar")
+          main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
+
+      # check that btrfs send/receive works and ssh access works
+      with subtest("remote"):
+          archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
+          main.succeed("echo baz > /mnt/to_backup/bar")
+          archive.succeed("cat /mnt/*/bar | grep bar")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/btrbk-no-timer.nix b/nixpkgs/nixos/tests/btrbk-no-timer.nix
new file mode 100644
index 000000000000..4fcab8839c89
--- /dev/null
+++ b/nixpkgs/nixos/tests/btrbk-no-timer.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+  {
+    name = "btrbk-no-timer";
+    meta.maintainers = with lib.maintainers; [ oxalica ];
+
+    nodes.machine = { ... }: {
+      environment.systemPackages = with pkgs; [ btrfs-progs ];
+      services.btrbk.instances.local = {
+        onCalendar = null;
+        settings.volume."/mnt" = {
+          snapshot_dir = "btrbk/local";
+          subvolume = "to_backup";
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # Create btrfs partition at /mnt
+      machine.succeed("truncate --size=128M /data_fs")
+      machine.succeed("mkfs.btrfs /data_fs")
+      machine.succeed("mkdir /mnt")
+      machine.succeed("mount /data_fs /mnt")
+      machine.succeed("btrfs subvolume create /mnt/to_backup")
+      machine.succeed("mkdir -p /mnt/btrbk/local")
+
+      # The service should not have any triggering timer.
+      unit = machine.get_unit_info('btrbk-local.service')
+      assert "TriggeredBy" not in unit
+
+      # Manually starting the service should still work.
+      machine.succeed("echo foo > /mnt/to_backup/bar")
+      machine.start_job("btrbk-local.service")
+      machine.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/btrbk-section-order.nix b/nixpkgs/nixos/tests/btrbk-section-order.nix
new file mode 100644
index 000000000000..6082de947f66
--- /dev/null
+++ b/nixpkgs/nixos/tests/btrbk-section-order.nix
@@ -0,0 +1,56 @@
+# This tests validates the order of generated sections that may contain
+# other sections.
+# When a `volume` section has both `subvolume` and `target` children,
+# `target` must go before `subvolume`. Otherwise, `target` will become
+# a child of the last `subvolume` instead of `volume`, due to the
+# order-sensitive config format.
+#
+# Issue: https://github.com/NixOS/nixpkgs/issues/195660
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "btrbk-section-order";
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine = { ... }: {
+    services.btrbk.instances.local = {
+      onCalendar = null;
+      settings = {
+        timestamp_format = "long";
+        target."ssh://global-target/".ssh_user = "root";
+        volume."/btrfs" = {
+          snapshot_dir = "/volume-snapshots";
+          target."ssh://volume-target/".ssh_user = "root";
+          subvolume."@subvolume" = {
+            snapshot_dir = "/subvolume-snapshots";
+            target."ssh://subvolume-target/".ssh_user = "root";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    import difflib
+    machine.wait_for_unit("basic.target")
+    got = machine.succeed("cat /etc/btrbk/local.conf").strip()
+    expect = """
+    backend btrfs-progs-sudo
+    stream_compress no
+    timestamp_format long
+    target ssh://global-target/
+     ssh_user root
+    volume /btrfs
+     snapshot_dir /volume-snapshots
+     target ssh://volume-target/
+      ssh_user root
+     subvolume @subvolume
+      snapshot_dir /subvolume-snapshots
+      target ssh://subvolume-target/
+       ssh_user root
+    """.strip()
+    print(got)
+    if got != expect:
+      diff = difflib.unified_diff(expect.splitlines(keepends=True), got.splitlines(keepends=True), fromfile="expected", tofile="got")
+      print("".join(diff))
+    assert got == expect
+  '';
+})
diff --git a/nixpkgs/nixos/tests/btrbk.nix b/nixpkgs/nixos/tests/btrbk.nix
new file mode 100644
index 000000000000..403c9595530d
--- /dev/null
+++ b/nixpkgs/nixos/tests/btrbk.nix
@@ -0,0 +1,111 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  let
+    privateKey = ''
+      -----BEGIN OPENSSH PRIVATE KEY-----
+      b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+      QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
+      RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
+      AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
+      9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
+      -----END OPENSSH PRIVATE KEY-----
+    '';
+    publicKey = ''
+      ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
+    '';
+  in
+  {
+    name = "btrbk";
+    meta = with pkgs.lib; {
+      maintainers = with maintainers; [ symphorien ];
+    };
+
+    nodes = {
+      archive = { ... }: {
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        # note: this makes the privateKey world readable.
+        # don't do it with real ssh keys.
+        environment.etc."btrbk_key".text = privateKey;
+        services.btrbk = {
+          instances = {
+            remote = {
+              onCalendar = "minutely";
+              settings = {
+                ssh_identity = "/etc/btrbk_key";
+                ssh_user = "btrbk";
+                stream_compress = "lz4";
+                volume = {
+                  "ssh://main/mnt" = {
+                    target = "/mnt";
+                    snapshot_dir = "btrbk/remote";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+
+      main = { ... }: {
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        services.openssh = {
+          enable = true;
+          settings = {
+            KbdInteractiveAuthentication = false;
+            PasswordAuthentication = false;
+          };
+        };
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          sshAccess = [
+            {
+              key = publicKey;
+              roles = [ "source" "send" "info" "delete" ];
+            }
+          ];
+          instances = {
+            local = {
+              onCalendar = "minutely";
+              settings = {
+                volume = {
+                  "/mnt" = {
+                    snapshot_dir = "btrbk/local";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # create btrfs partition at /mnt
+      for machine in (archive, main):
+        machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
+        machine.succeed("mkfs.btrfs /data_fs")
+        machine.succeed("mkdir /mnt")
+        machine.succeed("mount /data_fs /mnt")
+
+      # what to backup and where
+      main.succeed("btrfs subvolume create /mnt/to_backup")
+      main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
+
+      # check that local snapshots work
+      with subtest("local"):
+          main.succeed("echo foo > /mnt/to_backup/bar")
+          main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
+          main.succeed("echo bar > /mnt/to_backup/bar")
+          main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
+
+      # check that btrfs send/receive works and ssh access works
+      with subtest("remote"):
+          archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
+          main.succeed("echo baz > /mnt/to_backup/bar")
+          archive.succeed("cat /mnt/*/bar | grep bar")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/budgie.nix b/nixpkgs/nixos/tests/budgie.nix
new file mode 100644
index 000000000000..fe0ed2cf80ed
--- /dev/null
+++ b/nixpkgs/nixos/tests/budgie.nix
@@ -0,0 +1,67 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "budgie";
+
+  meta.maintainers = [ lib.maintainers.federicoschonborn ];
+
+  nodes.machine = { ... }: {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    services.xserver.enable = true;
+
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+
+    services.xserver.desktopManager.budgie = {
+      enable = true;
+      extraPlugins = [
+        pkgs.budgiePlugins.budgie-analogue-clock-applet
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+    in
+    ''
+      with subtest("Wait for login"):
+          # wait_for_x() checks graphical-session.target, which is expected to be
+          # inactive on Budgie before Budgie manages user session with systemd.
+          # https://github.com/BuddiesOfBudgie/budgie-desktop/blob/39e9f0895c978f76/src/session/budgie-desktop.in#L16
+          #
+          # Previously this was unconditionally touched by xsessionWrapper but was
+          # changed in #233981 (we have Budgie:GNOME in XDG_CURRENT_DESKTOP).
+          # machine.wait_for_x()
+          machine.wait_until_succeeds('journalctl -t budgie-session-binary --grep "Entering running state"')
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if Budgie session components actually start"):
+          machine.wait_until_succeeds("pgrep budgie-daemon")
+          machine.wait_for_window("budgie-daemon")
+          machine.wait_until_succeeds("pgrep budgie-panel")
+          machine.wait_for_window("budgie-panel")
+          # We don't check xwininfo for this one.
+          # See https://github.com/NixOS/nixpkgs/pull/216737#discussion_r1155312754
+          machine.wait_until_succeeds("pgrep budgie-wm")
+
+      with subtest("Open MATE terminal"):
+          machine.succeed("su - ${user.name} -c 'DISPLAY=:0 mate-terminal >&2 &'")
+          machine.wait_for_window("Terminal")
+
+      with subtest("Check if budgie-wm has ever coredumped"):
+          machine.fail("coredumpctl --json=short | grep budgie-wm")
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/buildbot.nix b/nixpkgs/nixos/tests/buildbot.nix
new file mode 100644
index 000000000000..149d73bba09c
--- /dev/null
+++ b/nixpkgs/nixos/tests/buildbot.nix
@@ -0,0 +1,110 @@
+# Test ensures buildbot master comes up correctly and workers can connect
+
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "buildbot";
+
+  nodes = {
+    bbmaster = { pkgs, ... }: {
+      services.buildbot-master = {
+        enable = true;
+
+        # NOTE: use fake repo due to no internet in hydra ci
+        factorySteps = [
+          "steps.Git(repourl='git://gitrepo/fakerepo.git', mode='incremental')"
+          "steps.ShellCommand(command=['bash', 'fakerepo.sh'])"
+        ];
+        changeSource = [
+          "changes.GitPoller('git://gitrepo/fakerepo.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
+        ];
+      };
+      networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
+      environment.systemPackages = with pkgs; [ git buildbot-full ];
+    };
+
+    bbworker = { pkgs, ... }: {
+      services.buildbot-worker = {
+        enable = true;
+        masterUrl = "bbmaster:9989";
+      };
+      environment.systemPackages = with pkgs; [ git buildbot-worker ];
+    };
+
+    gitrepo = { pkgs, ... }: {
+      services.openssh.enable = true;
+      networking.firewall.allowedTCPPorts = [ 22 9418 ];
+      environment.systemPackages = with pkgs; [ git ];
+      systemd.services.git-daemon = {
+        description   = "Git daemon for the test";
+        wantedBy      = [ "multi-user.target" ];
+        after         = [ "network.target" "sshd.service" ];
+
+        serviceConfig.Restart = "always";
+        path = with pkgs; [ coreutils git openssh ];
+        environment = { HOME = "/root"; };
+        preStart = ''
+          git config --global user.name 'Nobody Fakeuser'
+          git config --global user.email 'nobody\@fakerepo.com'
+          rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo
+          mkdir -pv /srv/repos/fakerepo ~/.ssh
+          ssh-keyscan -H gitrepo > ~/.ssh/known_hosts
+          cat ~/.ssh/known_hosts
+
+          mkdir -p /src/repos/fakerepo
+          cd /srv/repos/fakerepo
+          rm -rf *
+          git init
+          echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh
+          cat fakerepo.sh
+          touch .git/git-daemon-export-ok
+          git add fakerepo.sh .git/git-daemon-export-ok
+          git commit -m fakerepo
+        '';
+        script = ''
+          git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    gitrepo.wait_for_unit("git-daemon.service")
+    gitrepo.wait_for_unit("multi-user.target")
+
+    with subtest("Repo is accessible via git daemon"):
+        bbmaster.systemctl("start network-online.target")
+        bbmaster.wait_for_unit("network-online.target")
+        bbmaster.succeed("rm -rfv /tmp/fakerepo")
+        bbmaster.succeed("git clone git://gitrepo/fakerepo /tmp/fakerepo")
+
+    with subtest("Master service and worker successfully connect"):
+        bbmaster.wait_for_unit("buildbot-master.service")
+        bbmaster.wait_until_succeeds("curl --fail -s --head http://bbmaster:8010")
+        bbworker.systemctl("start network-online.target")
+        bbworker.wait_for_unit("network-online.target")
+        bbworker.succeed("nc -z bbmaster 8010")
+        bbworker.succeed("nc -z bbmaster 9989")
+        bbworker.wait_for_unit("buildbot-worker.service")
+
+    with subtest("Stop buildbot worker"):
+        bbmaster.succeed("systemctl -l --no-pager status buildbot-master")
+        bbmaster.succeed("systemctl stop buildbot-master")
+        bbworker.fail("nc -z bbmaster 8010")
+        bbworker.fail("nc -z bbmaster 9989")
+        bbworker.succeed("systemctl -l --no-pager status buildbot-worker")
+        bbworker.succeed("systemctl stop buildbot-worker")
+
+    with subtest("Buildbot daemon mode works"):
+        bbmaster.succeed(
+            "buildbot create-master /tmp",
+            "mv -fv /tmp/master.cfg.sample /tmp/master.cfg",
+            "sed -i 's/8010/8011/' /tmp/master.cfg",
+            "buildbot start /tmp",
+            "nc -z bbmaster 8011",
+        )
+        bbworker.wait_until_succeeds("curl --fail -s --head http://bbmaster:8011")
+        bbmaster.wait_until_succeeds("buildbot stop /tmp")
+        bbworker.fail("nc -z bbmaster 8011")
+  '';
+
+  meta.maintainers = pkgs.lib.teams.buildbot.members;
+})
diff --git a/nixpkgs/nixos/tests/buildkite-agents.nix b/nixpkgs/nixos/tests/buildkite-agents.nix
new file mode 100644
index 000000000000..a5abfdb5e2e5
--- /dev/null
+++ b/nixpkgs/nixos/tests/buildkite-agents.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "buildkite-agent";
+  meta.maintainers = with lib.maintainers; [ flokli ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.buildkite-agents = {
+      one = {
+        privateSshKeyPath = (import ./ssh-keys.nix pkgs).snakeOilPrivateKey;
+        tokenPath = (pkgs.writeText "my-token" "5678");
+      };
+      two = {
+        tokenPath = (pkgs.writeText "my-token" "1234");
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    # we can't wait on the unit to start up, as we obviously can't connect to buildkite,
+    # but we can look whether files are set up correctly
+
+    machine.wait_for_file("/var/lib/buildkite-agent-one/buildkite-agent.cfg")
+    machine.wait_for_file("/var/lib/buildkite-agent-one/.ssh/id_rsa")
+
+    machine.wait_for_file("/var/lib/buildkite-agent-two/buildkite-agent.cfg")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/c2fmzq.nix b/nixpkgs/nixos/tests/c2fmzq.nix
new file mode 100644
index 000000000000..0dd89f6881dd
--- /dev/null
+++ b/nixpkgs/nixos/tests/c2fmzq.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "c2FmZQ";
+  meta.maintainers = with lib.maintainers; [ hmenke ];
+
+  nodes.machine = {
+    services.c2fmzq-server = {
+      enable = true;
+      port = 8080;
+      passphraseFile = builtins.toFile "pwfile" "hunter2"; # don't do this on real deployments
+      settings = {
+        verbose = 3; # debug
+        # make sure multiple freeform options evaluate
+        allow-new-accounts = true;
+        auto-approve-new-accounts = true;
+        licenses = false;
+      };
+    };
+    environment = {
+      sessionVariables = {
+        C2FMZQ_PASSPHRASE = "lol";
+        C2FMZQ_API_SERVER = "http://localhost:8080";
+      };
+      systemPackages = [
+        pkgs.c2fmzq
+        (pkgs.writeScriptBin "c2FmZQ-client-wrapper" ''
+          #!${pkgs.expect}/bin/expect -f
+          spawn c2FmZQ-client {*}$argv
+          expect {
+            "Enter password:" { send "$env(PASSWORD)\r" }
+            "Type YES to confirm:" { send "YES\r" }
+            timeout { exit 1 }
+            eof { exit 0 }
+          }
+          interact
+        '')
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    machine.start()
+    machine.wait_for_unit("c2fmzq-server.service")
+    machine.wait_for_open_port(8080)
+
+    with subtest("Create accounts for alice and bob"):
+        machine.succeed("PASSWORD=foobar c2FmZQ-client-wrapper -- -v 3 create-account alice@example.com")
+        machine.succeed("PASSWORD=fizzbuzz c2FmZQ-client-wrapper -- -v 3 create-account bob@example.com")
+
+    with subtest("Log in as alice"):
+        machine.succeed("PASSWORD=foobar c2FmZQ-client-wrapper -- -v 3 login alice@example.com")
+        msg = machine.succeed("c2FmZQ-client -v 3 status")
+        assert "Logged in as alice@example.com" in msg, f"ERROR: Not logged in as alice:\n{msg}"
+
+    with subtest("Create a new album, upload a file, and delete the uploaded file"):
+        machine.succeed("c2FmZQ-client -v 3 create-album 'Rarest Memes'")
+        machine.succeed("echo 'pls do not steal' > meme.txt")
+        machine.succeed("c2FmZQ-client -v 3 import meme.txt 'Rarest Memes'")
+        machine.succeed("c2FmZQ-client -v 3 sync")
+        machine.succeed("rm meme.txt")
+
+    with subtest("Share the album with bob"):
+        machine.succeed("c2FmZQ-client-wrapper -- -v 3 share 'Rarest Memes' bob@example.com")
+
+    with subtest("Log in as bob"):
+        machine.succeed("PASSWORD=fizzbuzz c2FmZQ-client-wrapper -- -v 3 login bob@example.com")
+        msg = machine.succeed("c2FmZQ-client -v 3 status")
+        assert "Logged in as bob@example.com" in msg, f"ERROR: Not logged in as bob:\n{msg}"
+
+    with subtest("Download the shared file"):
+        machine.succeed("c2FmZQ-client -v 3 download 'shared/Rarest Memes/meme.txt'")
+        machine.succeed("c2FmZQ-client -v 3 export 'shared/Rarest Memes/meme.txt' .")
+        msg = machine.succeed("cat meme.txt")
+        assert "pls do not steal\n" == msg, f"File content is not the same:\n{msg}"
+
+    with subtest("Test that PWA is served"):
+        msg = machine.succeed("curl -sSfL http://localhost:8080")
+        assert "c2FmZQ" in msg, f"Could not find 'c2FmZQ' in the output:\n{msg}"
+
+    with subtest("A setting with false value is properly passed"):
+        machine.succeed("systemctl show -p ExecStart --value c2fmzq-server.service | grep -F -- '--licenses=false'");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/caddy.nix b/nixpkgs/nixos/tests/caddy.nix
new file mode 100644
index 000000000000..41d8e57de468
--- /dev/null
+++ b/nixpkgs/nixos/tests/caddy.nix
@@ -0,0 +1,103 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "caddy";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ xfix Br1ght0ne ];
+  };
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.caddy.enable = true;
+      services.caddy.extraConfig = ''
+        http://localhost {
+          encode gzip
+
+          file_server
+          root * ${
+            pkgs.runCommand "testdir" {} ''
+              mkdir "$out"
+              echo hello world > "$out/example.html"
+            ''
+          }
+        }
+      '';
+      services.caddy.enableReload = true;
+
+      specialisation.config-reload.configuration = {
+        services.caddy.extraConfig = ''
+          http://localhost:8080 {
+          }
+        '';
+      };
+      specialisation.multiple-configs.configuration = {
+        services.caddy.virtualHosts = {
+          "http://localhost:8080" = { };
+          "http://localhost:8081" = { };
+        };
+      };
+      specialisation.rfc42.configuration = {
+        services.caddy.settings = {
+          apps.http.servers.default = {
+            listen = [ ":80" ];
+            routes = [{
+              handle = [{
+                body = "hello world";
+                handler = "static_response";
+                status_code = 200;
+              }];
+            }];
+          };
+        };
+      };
+      specialisation.explicit-config-file.configuration = {
+        services.caddy.configFile = pkgs.writeText "Caddyfile" ''
+        localhost:80
+
+        respond "hello world"
+        '';
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      explicitConfigFile = "${nodes.webserver.system.build.toplevel}/specialisation/explicit-config-file";
+      justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/config-reload";
+      multipleConfigs = "${nodes.webserver.system.build.toplevel}/specialisation/multiple-configs";
+      rfc42Config = "${nodes.webserver.system.build.toplevel}/specialisation/rfc42";
+    in
+    ''
+      url = "http://localhost/example.html"
+      webserver.wait_for_unit("caddy")
+      webserver.wait_for_open_port(80)
+
+
+      with subtest("config is reloaded on nixos-rebuild switch"):
+          webserver.succeed(
+              "${justReloadSystem}/bin/switch-to-configuration test >&2"
+          )
+          webserver.wait_for_open_port(8080)
+          webserver.fail("journalctl -u caddy | grep -q -i stopped")
+          webserver.succeed("journalctl -u caddy | grep -q -i reloaded")
+
+      with subtest("multiple configs are correctly merged"):
+          webserver.succeed(
+              "${multipleConfigs}/bin/switch-to-configuration test >&2"
+          )
+          webserver.wait_for_open_port(8080)
+          webserver.wait_for_open_port(8081)
+
+      with subtest("rfc42 settings config"):
+          webserver.succeed(
+              "${rfc42Config}/bin/switch-to-configuration test >&2"
+          )
+          webserver.wait_for_open_port(80)
+          webserver.succeed("curl http://localhost | grep hello")
+
+      with subtest("explicit configFile"):
+          webserver.succeed(
+              "${explicitConfigFile}/bin/switch-to-configuration test >&2"
+          )
+          webserver.wait_for_open_port(80)
+          webserver.succeed("curl http://localhost | grep hello")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/cadvisor.nix b/nixpkgs/nixos/tests/cadvisor.nix
new file mode 100644
index 000000000000..70e068fcf21c
--- /dev/null
+++ b/nixpkgs/nixos/tests/cadvisor.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ lib, pkgs, ... } : {
+  name = "cadvisor";
+  meta.maintainers = with lib.maintainers; [ offline ];
+
+  nodes = {
+    machine = { ... }: {
+      services.cadvisor.enable = true;
+    };
+
+    influxdb = { lib, ... }: {
+      services.cadvisor.enable = true;
+      services.cadvisor.storageDriver = "influxdb";
+      services.influxdb.enable = true;
+    };
+  };
+
+  testScript =  ''
+      start_all()
+      machine.wait_for_unit("cadvisor.service")
+      machine.succeed("curl -f http://localhost:8080/containers/")
+
+      influxdb.wait_for_unit("influxdb.service")
+
+      # create influxdb database
+      influxdb.succeed(
+          'curl -f -XPOST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE root"'
+      )
+
+      influxdb.wait_for_unit("cadvisor.service")
+      influxdb.succeed("curl -f http://localhost:8080/containers/")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/cage.nix b/nixpkgs/nixos/tests/cage.nix
new file mode 100644
index 000000000000..3b49185124f3
--- /dev/null
+++ b/nixpkgs/nixos/tests/cage.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "cage";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ matthewbauer ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+
+    fonts.packages = with pkgs; [ dejavu_fonts ];
+
+    services.cage = {
+      enable = true;
+      user = "alice";
+      program = "${pkgs.xterm}/bin/xterm";
+    };
+
+    # Need to switch to a different GPU driver than the default one (-vga std) so that Cage can launch:
+    virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+  in ''
+    with subtest("Wait for cage to boot up"):
+        start_all()
+        machine.wait_for_file("/run/user/${toString user.uid}/wayland-0.lock")
+        machine.wait_until_succeeds("pgrep xterm")
+        machine.wait_for_text("alice@machine")
+        machine.screenshot("screen")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/cagebreak.nix b/nixpkgs/nixos/tests/cagebreak.nix
new file mode 100644
index 000000000000..1fef7cb57cfc
--- /dev/null
+++ b/nixpkgs/nixos/tests/cagebreak.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+let
+  cagebreakConfigfile = pkgs.writeText "config" ''
+    workspaces 1
+    escape C-t
+    bind t exec env DISPLAY=:0 ${pkgs.xterm}/bin/xterm -cm -pc
+  '';
+in
+{
+  name = "cagebreak";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ berbiche ];
+  };
+
+  nodes.machine = { config, ... }:
+  let
+    alice = config.users.users.alice;
+  in {
+    # Automatically login on tty1 as a normal user:
+    imports = [ ./common/user-account.nix ];
+    services.getty.autologinUser = "alice";
+    programs.bash.loginShellInit = ''
+      if [ "$(tty)" = "/dev/tty1" ]; then
+        set -e
+
+        mkdir -p ~/.config/cagebreak
+        cp -f ${cagebreakConfigfile} ~/.config/cagebreak/config
+
+        cagebreak
+      fi
+    '';
+
+    hardware.opengl.enable = true;
+    programs.xwayland.enable = true;
+    security.polkit.enable = true;
+    environment.systemPackages = [ pkgs.cagebreak pkgs.wayland-utils ];
+
+    # Need to switch to a different GPU driver than the default one (-vga std) so that Cagebreak can launch:
+    virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+    XDG_RUNTIME_DIR = "/run/user/${toString user.uid}";
+  in ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_file("${XDG_RUNTIME_DIR}/wayland-0")
+
+    with subtest("ensure wayland works with wayinfo from wallutils"):
+        print(machine.succeed("env XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR} wayland-info"))
+
+    # TODO: Fix the XWayland test (log the cagebreak output to debug):
+    # with subtest("ensure xwayland works with xterm"):
+    #     machine.send_key("ctrl-t")
+    #     machine.send_key("t")
+    #     machine.wait_until_succeeds("pgrep xterm")
+    #     machine.wait_for_text("${user.name}@machine")
+    #     machine.screenshot("screen")
+    #     machine.send_key("ctrl-d")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/calibre-server.nix b/nixpkgs/nixos/tests/calibre-server.nix
new file mode 100644
index 000000000000..4b1753aaa704
--- /dev/null
+++ b/nixpkgs/nixos/tests/calibre-server.nix
@@ -0,0 +1,104 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) concatStringsSep maintainers mapAttrs mkMerge
+    removeSuffix splitString;
+
+  tests = {
+    default = {
+      calibreConfig = {};
+      calibreScript = ''
+        wait_for_unit("calibre-server.service")
+      '';
+    };
+    customLibrary = {
+      calibreConfig = {
+        libraries = [ "/var/lib/calibre-data" ];
+      };
+      calibreScript = ''
+        succeed("ls -la /var/lib/calibre-data")
+        wait_for_unit("calibre-server.service")
+      '';
+    };
+    multipleLibraries = {
+      calibreConfig = {
+        libraries = [ "/var/lib/calibre-data" "/var/lib/calibre-server" ];
+      };
+      calibreScript = ''
+        succeed("ls -la /var/lib/calibre-data")
+        succeed("ls -la /var/lib/calibre-server")
+        wait_for_unit("calibre-server.service")
+      '';
+    };
+    hostAndPort = {
+      calibreConfig = {
+        host = "127.0.0.1";
+        port = 8888;
+      };
+      calibreScript = ''
+        wait_for_unit("calibre-server.service")
+        wait_for_open_port(8888)
+        succeed("curl --fail http://127.0.0.1:8888")
+      '';
+    };
+    basicAuth = {
+      calibreConfig = {
+        host = "127.0.0.1";
+        port = 8888;
+        auth = {
+          enable = true;
+          mode = "basic";
+        };
+      };
+      calibreScript = ''
+        wait_for_unit("calibre-server.service")
+        wait_for_open_port(8888)
+        fail("curl --fail http://127.0.0.1:8888")
+      '';
+    };
+  };
+in
+mapAttrs
+  (test: testConfig: (makeTest (
+    let
+      nodeName = testConfig.nodeName or test;
+      calibreConfig = {
+        enable = true;
+        libraries = [ "/var/lib/calibre-server" ];
+      } // testConfig.calibreConfig or {};
+      librariesInitScript = path: ''
+        ${nodeName}.execute("touch /tmp/test.epub")
+        ${nodeName}.execute("zip -r /tmp/test.zip /tmp/test.epub")
+        ${nodeName}.execute("mkdir -p ${path}")
+        ${nodeName}.execute("calibredb add -d --with-library ${path} /tmp/test.zip")
+      '';
+    in
+    {
+      name = "calibre-server-${test}";
+
+      nodes.${nodeName} = mkMerge [{
+        environment.systemPackages = [ pkgs.zip ];
+        services.calibre-server = calibreConfig;
+      } testConfig.calibreProvider or { }];
+
+      testScript = ''
+        ${nodeName}.start()
+        ${concatStringsSep "\n" (map librariesInitScript calibreConfig.libraries)}
+        ${concatStringsSep "\n" (map (line:
+          if (builtins.substring 0 1 line == " " || builtins.substring 0 1 line == ")")
+          then line
+          else "${nodeName}.${line}"
+        ) (splitString "\n" (removeSuffix "\n" testConfig.calibreScript)))}
+        ${nodeName}.shutdown()
+      '';
+
+      meta = with maintainers; {
+        maintainers = [ gaelreyrol ];
+      };
+    }
+  )))
+  tests
diff --git a/nixpkgs/nixos/tests/calibre-web.nix b/nixpkgs/nixos/tests/calibre-web.nix
new file mode 100644
index 000000000000..aea9bca3ebe9
--- /dev/null
+++ b/nixpkgs/nixos/tests/calibre-web.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+
+    let
+      port = 3142;
+      defaultPort = 8083;
+    in
+      {
+        name = "calibre-web";
+        meta.maintainers = with lib.maintainers; [ pborzenkov ];
+
+        nodes = {
+          customized = { pkgs, ... }: {
+            services.calibre-web = {
+              enable = true;
+              listen.port = port;
+              options = {
+                calibreLibrary = "/tmp/books";
+                reverseProxyAuth = {
+                  enable = true;
+                  header = "X-User";
+                };
+              };
+            };
+            environment.systemPackages = [ pkgs.calibre ];
+          };
+        };
+        testScript = ''
+          start_all()
+
+          customized.succeed(
+              "mkdir /tmp/books && calibredb --library-path /tmp/books add -e --title test-book"
+          )
+          customized.succeed("systemctl restart calibre-web")
+          customized.wait_for_unit("calibre-web.service")
+          customized.wait_for_open_port(${toString port})
+          customized.succeed(
+              "curl --fail -H X-User:admin 'http://localhost:${toString port}' | grep test-book"
+          )
+        '';
+      }
+)
diff --git a/nixpkgs/nixos/tests/cassandra.nix b/nixpkgs/nixos/tests/cassandra.nix
new file mode 100644
index 000000000000..a19d525c3431
--- /dev/null
+++ b/nixpkgs/nixos/tests/cassandra.nix
@@ -0,0 +1,132 @@
+import ./make-test-python.nix ({ pkgs, lib, testPackage ? pkgs.cassandra, ... }:
+let
+  clusterName = "NixOS Automated-Test Cluster";
+
+  testRemoteAuth = lib.versionAtLeast testPackage.version "3.11";
+  jmxRoles = [{ username = "me"; password = "password"; }];
+  jmxRolesFile = ./cassandra-jmx-roles;
+  jmxAuthArgs = "-u ${(builtins.elemAt jmxRoles 0).username} -pw ${(builtins.elemAt jmxRoles 0).password}";
+  jmxPort = 7200;  # Non-standard port so it doesn't accidentally work
+  jmxPortStr = toString jmxPort;
+
+  # Would usually be assigned to 512M.
+  # Set it to a different value, so that we can check whether our config
+  # actually changes it.
+  numMaxHeapSize = "400";
+  getHeapLimitCommand = ''
+    nodetool info -p ${jmxPortStr} | grep "^Heap Memory" | awk '{print $NF}'
+  '';
+  checkHeapLimitCommand = pkgs.writeShellScript "check-heap-limit.sh" ''
+    [ 1 -eq "$(echo "$(${getHeapLimitCommand}) < ${numMaxHeapSize}" | ${pkgs.bc}/bin/bc)" ]
+  '';
+
+  cassandraCfg = ipAddress:
+    { enable = true;
+      inherit clusterName;
+      listenAddress = ipAddress;
+      rpcAddress = ipAddress;
+      seedAddresses = [ "192.168.1.1" ];
+      package = testPackage;
+      maxHeapSize = "${numMaxHeapSize}M";
+      heapNewSize = "100M";
+      inherit jmxPort;
+    };
+  nodeCfg = ipAddress: extra: {pkgs, config, ...}: rec {
+    environment.systemPackages = [ testPackage ];
+    networking = {
+      firewall.allowedTCPPorts = [ 7000 9042 services.cassandra.jmxPort ];
+      useDHCP = false;
+      interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+        { address = ipAddress; prefixLength = 24; }
+      ];
+    };
+    services.cassandra = cassandraCfg ipAddress // extra;
+  };
+in
+{
+  name = "cassandra-${testPackage.version}";
+  meta = {
+    maintainers = with lib.maintainers; [ johnazoidberg ];
+  };
+
+  nodes = {
+    cass0 = nodeCfg "192.168.1.1" {};
+    cass1 = nodeCfg "192.168.1.2" (lib.optionalAttrs testRemoteAuth { inherit jmxRoles; remoteJmx = true; });
+    cass2 = nodeCfg "192.168.1.3" { jvmOpts = [ "-Dcassandra.replace_address=cass1" ]; };
+  };
+
+  testScript = ''
+    # Check configuration
+    with subtest("Timers exist"):
+        cass0.succeed("systemctl list-timers | grep cassandra-full-repair.timer")
+        cass0.succeed("systemctl list-timers | grep cassandra-incremental-repair.timer")
+
+    with subtest("Can connect via cqlsh"):
+        cass0.wait_for_unit("cassandra.service")
+        cass0.wait_until_succeeds("nc -z cass0 9042")
+        cass0.succeed("echo 'show version;' | cqlsh cass0")
+
+    with subtest("Nodetool is operational"):
+        cass0.wait_for_unit("cassandra.service")
+        cass0.wait_until_succeeds("nc -z localhost ${jmxPortStr}")
+        cass0.succeed("nodetool status -p ${jmxPortStr} --resolve-ip | egrep '^UN[[:space:]]+cass0'")
+
+    with subtest("Cluster name was set"):
+        cass0.wait_for_unit("cassandra.service")
+        cass0.wait_until_succeeds("nc -z localhost ${jmxPortStr}")
+        cass0.wait_until_succeeds(
+            "nodetool describecluster -p ${jmxPortStr} | grep 'Name: ${clusterName}'"
+        )
+
+    with subtest("Heap limit set correctly"):
+        # Nodetool takes a while until it can display info
+        cass0.wait_until_succeeds("nodetool info -p ${jmxPortStr}")
+        cass0.succeed("${checkHeapLimitCommand}")
+
+    # Check cluster interaction
+    with subtest("Bring up cluster"):
+        cass1.wait_for_unit("cassandra.service")
+        cass1.wait_until_succeeds(
+            "nodetool -p ${jmxPortStr} ${jmxAuthArgs} status | egrep -c '^UN' | grep 2"
+        )
+        cass0.succeed("nodetool status -p ${jmxPortStr} --resolve-ip | egrep '^UN[[:space:]]+cass1'")
+  '' + lib.optionalString testRemoteAuth ''
+    with subtest("Remote authenticated jmx"):
+        # Doesn't work if not enabled
+        cass0.wait_until_succeeds("nc -z localhost ${jmxPortStr}")
+        cass1.fail("nc -z 192.168.1.1 ${jmxPortStr}")
+        cass1.fail("nodetool -p ${jmxPortStr} -h 192.168.1.1 status")
+
+        # Works if enabled
+        cass1.wait_until_succeeds("nc -z localhost ${jmxPortStr}")
+        cass0.succeed("nodetool -p ${jmxPortStr} -h 192.168.1.2 ${jmxAuthArgs} status")
+  '' + ''
+    with subtest("Break and fix node"):
+        cass1.block()
+        cass0.wait_until_succeeds(
+            "nodetool status -p ${jmxPortStr} --resolve-ip | egrep -c '^DN[[:space:]]+cass1'"
+        )
+        cass0.succeed("nodetool status -p ${jmxPortStr} | egrep -c '^UN'  | grep 1")
+        cass1.unblock()
+        cass1.wait_until_succeeds(
+            "nodetool -p ${jmxPortStr} ${jmxAuthArgs} status | egrep -c '^UN'  | grep 2"
+        )
+        cass0.succeed("nodetool status -p ${jmxPortStr} | egrep -c '^UN'  | grep 2")
+
+    with subtest("Replace crashed node"):
+        cass1.block()  # .crash() waits until it's fully shutdown
+        cass2.start()
+        cass0.wait_until_fails(
+            "nodetool status -p ${jmxPortStr} --resolve-ip | egrep '^UN[[:space:]]+cass1'"
+        )
+
+        cass2.wait_for_unit("cassandra.service")
+        cass0.wait_until_succeeds(
+            "nodetool status -p ${jmxPortStr} --resolve-ip | egrep '^UN[[:space:]]+cass2'"
+        )
+  '';
+
+  passthru = {
+    inherit testPackage;
+  };
+})
diff --git a/nixpkgs/nixos/tests/castopod.nix b/nixpkgs/nixos/tests/castopod.nix
new file mode 100644
index 000000000000..4435ec617d4e
--- /dev/null
+++ b/nixpkgs/nixos/tests/castopod.nix
@@ -0,0 +1,87 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "castopod";
+  meta = with lib.maintainers; {
+    maintainers = [ alexoundos misuzu ];
+  };
+  nodes.castopod = { nodes, ... }: {
+    networking.firewall.allowedTCPPorts = [ 80 ];
+    networking.extraHosts = ''
+      127.0.0.1 castopod.example.com
+    '';
+    services.castopod = {
+      enable = true;
+      database.createLocally = true;
+      localDomain = "castopod.example.com";
+    };
+    environment.systemPackages =
+      let
+        username = "admin";
+        email = "admin@castood.example.com";
+        password = "v82HmEp5";
+        testRunner = pkgs.writers.writePython3Bin "test-runner"
+          {
+            libraries = [ pkgs.python3Packages.selenium ];
+            flakeIgnore = [
+              "E501"
+            ];
+          } ''
+          from selenium.webdriver.common.by import By
+          from selenium.webdriver import Firefox
+          from selenium.webdriver.firefox.options import Options
+          from selenium.webdriver.support.ui import WebDriverWait
+          from selenium.webdriver.support import expected_conditions as EC
+
+          options = Options()
+          options.add_argument('--headless')
+          driver = Firefox(options=options)
+          try:
+              driver.implicitly_wait(20)
+              driver.get('http://castopod.example.com/cp-install')
+
+              wait = WebDriverWait(driver, 10)
+
+              wait.until(EC.title_contains("installer"))
+
+              driver.find_element(By.CSS_SELECTOR, '#username').send_keys(
+                  '${username}'
+              )
+              driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
+                  '${email}'
+              )
+              driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
+                  '${password}'
+              )
+              driver.find_element(By.XPATH, "//button[contains(., 'Finish install')]").click()
+
+              wait.until(EC.title_contains("Auth"))
+
+              driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
+                  '${email}'
+              )
+              driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
+                  '${password}'
+              )
+              driver.find_element(By.XPATH, "//button[contains(., 'Login')]").click()
+
+              wait.until(EC.title_contains("Admin dashboard"))
+          finally:
+              driver.close()
+              driver.quit()
+        '';
+      in
+      [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
+  };
+  testScript = ''
+    start_all()
+    castopod.wait_for_unit("castopod-setup.service")
+    castopod.wait_for_file("/run/phpfpm/castopod.sock")
+    castopod.wait_for_unit("nginx.service")
+    castopod.wait_for_open_port(80)
+    castopod.wait_until_succeeds("curl -sS -f http://castopod.example.com")
+    castopod.succeed("curl -s http://localhost/cp-install | grep 'Create your Super Admin account' > /dev/null")
+
+    with subtest("Create superadmin and log in"):
+        castopod.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ccache.nix b/nixpkgs/nixos/tests/ccache.nix
new file mode 100644
index 000000000000..a97ae0501767
--- /dev/null
+++ b/nixpkgs/nixos/tests/ccache.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "ccache";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ehmry ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    environment.systemPackages = [ pkgs.hello ];
+    programs.ccache = {
+      enable = true;
+      packageNames = [ "hello" ];
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("nix-ccache --show-stats")
+      machine.succeed("hello")
+      machine.shutdown()
+    '';
+})
diff --git a/nixpkgs/nixos/tests/centrifugo.nix b/nixpkgs/nixos/tests/centrifugo.nix
new file mode 100644
index 000000000000..45c2904f5585
--- /dev/null
+++ b/nixpkgs/nixos/tests/centrifugo.nix
@@ -0,0 +1,80 @@
+let
+  redisPort = 6379;
+  centrifugoPort = 8080;
+  nodes = [
+    "centrifugo1"
+    "centrifugo2"
+    "centrifugo3"
+  ];
+in
+{ lib, ... }: {
+  name = "centrifugo";
+  meta.maintainers = [ lib.maintainers.tie ];
+
+  nodes = lib.listToAttrs (lib.imap0
+    (index: name: {
+      inherit name;
+      value = { config, ... }: {
+        services.centrifugo = {
+          enable = true;
+          settings = {
+            inherit name;
+            port = centrifugoPort;
+            # See https://centrifugal.dev/docs/server/engines#redis-sharding
+            engine = "redis";
+            # Connect to local Redis shard via Unix socket.
+            redis_address =
+              let
+                otherNodes = lib.take index nodes ++ lib.drop (index + 1) nodes;
+              in
+              map (name: "${name}:${toString redisPort}") otherNodes ++ [
+                "unix://${config.services.redis.servers.centrifugo.unixSocket}"
+              ];
+            usage_stats_disable = true;
+            api_insecure = true;
+          };
+          extraGroups = [
+            config.services.redis.servers.centrifugo.user
+          ];
+        };
+        services.redis.servers.centrifugo = {
+          enable = true;
+          bind = null; # all interfaces
+          port = redisPort;
+          openFirewall = true;
+          settings.protected-mode = false;
+        };
+      };
+    })
+    nodes);
+
+  testScript = ''
+    import json
+
+    redisPort = ${toString redisPort}
+    centrifugoPort = ${toString centrifugoPort}
+
+    start_all()
+
+    for machine in machines:
+      machine.wait_for_unit("redis-centrifugo.service")
+      machine.wait_for_open_port(redisPort)
+
+    for machine in machines:
+      machine.wait_for_unit("centrifugo.service")
+      machine.wait_for_open_port(centrifugoPort)
+
+    # See https://centrifugal.dev/docs/server/server_api#info
+    def list_nodes(machine):
+      curl = "curl --fail-with-body --silent"
+      body = "{}"
+      resp = json.loads(machine.succeed(f"{curl} -d '{body}' http://localhost:{centrifugoPort}/api/info"))
+      return resp["result"]["nodes"]
+    machineNames = {m.name for m in machines}
+    for machine in machines:
+      nodes = list_nodes(machine)
+      assert len(nodes) == len(machines)
+      nodeNames = {n['name'] for n in nodes}
+      assert machineNames == nodeNames
+  '';
+}
diff --git a/nixpkgs/nixos/tests/ceph-multi-node.nix b/nixpkgs/nixos/tests/ceph-multi-node.nix
new file mode 100644
index 000000000000..b1352a4bc8f4
--- /dev/null
+++ b/nixpkgs/nixos/tests/ceph-multi-node.nix
@@ -0,0 +1,241 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+
+let
+  cfg = {
+    clusterId = "066ae264-2a5d-4729-8001-6ad265f50b03";
+    monA = {
+      name = "a";
+      ip = "192.168.1.1";
+    };
+    osd0 = {
+      name = "0";
+      ip = "192.168.1.2";
+      key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
+      uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
+    };
+    osd1 = {
+      name = "1";
+      ip = "192.168.1.3";
+      key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
+      uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
+    };
+    osd2 = {
+      name = "2";
+      ip = "192.168.1.4";
+      key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
+      uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
+    };
+  };
+  generateCephConfig = { daemonConfig }: {
+    enable = true;
+    global = {
+      fsid = cfg.clusterId;
+      monHost = cfg.monA.ip;
+      monInitialMembers = cfg.monA.name;
+    };
+  } // daemonConfig;
+
+  generateHost = { pkgs, cephConfig, networkConfig, ... }: {
+    virtualisation = {
+      emptyDiskImages = [ 20480 ];
+      vlans = [ 1 ];
+    };
+
+    networking = networkConfig;
+
+    environment.systemPackages = with pkgs; [
+      bash
+      sudo
+      ceph
+      xfsprogs
+      libressl.nc
+    ];
+
+    boot.kernelModules = [ "xfs" ];
+
+    services.ceph = cephConfig;
+  };
+
+  networkMonA = {
+    dhcpcd.enable = false;
+    interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+      { address = cfg.monA.ip; prefixLength = 24; }
+    ];
+    firewall = {
+      allowedTCPPorts = [ 6789 3300 ];
+      allowedTCPPortRanges = [ { from = 6800; to = 7300; } ];
+    };
+  };
+  cephConfigMonA = generateCephConfig { daemonConfig = {
+    mon = {
+      enable = true;
+      daemons = [ cfg.monA.name ];
+    };
+    mgr = {
+      enable = true;
+      daemons = [ cfg.monA.name ];
+    };
+  }; };
+
+  networkOsd = osd: {
+    dhcpcd.enable = false;
+    interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+      { address = osd.ip; prefixLength = 24; }
+    ];
+    firewall = {
+      allowedTCPPortRanges = [ { from = 6800; to = 7300; } ];
+    };
+  };
+
+  cephConfigOsd = osd: generateCephConfig { daemonConfig = {
+    osd = {
+      enable = true;
+      daemons = [ osd.name ];
+    };
+  }; };
+
+  # Following deployment is based on the manual deployment described here:
+  # https://docs.ceph.com/docs/master/install/manual-deployment/
+  # For other ways to deploy a ceph cluster, look at the documentation at
+  # https://docs.ceph.com/docs/master/
+  testscript = { ... }: ''
+    start_all()
+
+    monA.wait_for_unit("network.target")
+    osd0.wait_for_unit("network.target")
+    osd1.wait_for_unit("network.target")
+    osd2.wait_for_unit("network.target")
+
+    # Bootstrap ceph-mon daemon
+    monA.succeed(
+        "sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
+        "sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
+        "sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
+        "monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
+        "sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
+        "sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
+        "sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
+        "systemctl start ceph-mon-${cfg.monA.name}",
+    )
+    monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
+    monA.succeed("ceph mon enable-msgr2")
+    monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
+
+    # Can't check ceph status until a mon is up
+    monA.succeed("ceph -s | grep 'mon: 1 daemons'")
+
+    # Start the ceph-mgr daemon, it has no deps and hardly any setup
+    monA.succeed(
+        "ceph auth get-or-create mgr.${cfg.monA.name} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-${cfg.monA.name}/keyring",
+        "systemctl start ceph-mgr-${cfg.monA.name}",
+    )
+    monA.wait_for_unit("ceph-mgr-a")
+    monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+
+    # Send the admin keyring to the OSD machines
+    monA.succeed("cp /etc/ceph/ceph.client.admin.keyring /tmp/shared")
+    osd0.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
+    osd1.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
+    osd2.succeed("cp /tmp/shared/ceph.client.admin.keyring /etc/ceph")
+
+    # Bootstrap OSDs
+    osd0.succeed(
+        "mkfs.xfs /dev/vdb",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
+        "mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
+        'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
+    )
+    osd1.succeed(
+        "mkfs.xfs /dev/vdb",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
+        "mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
+        'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
+    )
+    osd2.succeed(
+        "mkfs.xfs /dev/vdb",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
+        "mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
+        'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
+    )
+
+    # Initialize the OSDs with regular filestore
+    osd0.succeed(
+        "ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
+        "chown -R ceph:ceph /var/lib/ceph/osd",
+        "systemctl start ceph-osd-${cfg.osd0.name}",
+    )
+    osd1.succeed(
+        "ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
+        "chown -R ceph:ceph /var/lib/ceph/osd",
+        "systemctl start ceph-osd-${cfg.osd1.name}",
+    )
+    osd2.succeed(
+        "ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
+        "chown -R ceph:ceph /var/lib/ceph/osd",
+        "systemctl start ceph-osd-${cfg.osd2.name}",
+    )
+    monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+
+    monA.succeed(
+        "ceph osd pool create multi-node-test 32 32",
+        "ceph osd pool ls | grep 'multi-node-test'",
+
+        # We need to enable an application on the pool, otherwise it will
+        # stay unhealthy in state POOL_APP_NOT_ENABLED.
+        # Creating a CephFS would do this automatically, but we haven't done that here.
+        # See: https://docs.ceph.com/en/reef/rados/operations/pools/#associating-a-pool-with-an-application
+        # We use the custom application name "nixos-test" for this.
+        "ceph osd pool application enable multi-node-test nixos-test",
+
+        "ceph osd pool rename multi-node-test multi-node-other-test",
+        "ceph osd pool ls | grep 'multi-node-other-test'",
+    )
+    monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
+    monA.succeed("ceph osd pool set multi-node-other-test size 2")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+    monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
+    monA.fail(
+        "ceph osd pool ls | grep 'multi-node-test'",
+        "ceph osd pool delete multi-node-other-test multi-node-other-test --yes-i-really-really-mean-it",
+    )
+
+    # Shut down ceph on all machines in a very unpolite way
+    monA.crash()
+    osd0.crash()
+    osd1.crash()
+    osd2.crash()
+
+    # Start it up
+    osd0.start()
+    osd1.start()
+    osd2.start()
+    monA.start()
+
+    # Ensure the cluster comes back up again
+    monA.succeed("ceph -s | grep 'mon: 1 daemons'")
+    monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
+    monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+  '';
+in {
+  name = "basic-multi-node-ceph-cluster";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lejonet ];
+  };
+
+  nodes = {
+    monA = generateHost { pkgs = pkgs; cephConfig = cephConfigMonA; networkConfig = networkMonA; };
+    osd0 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd0; networkConfig = networkOsd cfg.osd0; };
+    osd1 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd1; networkConfig = networkOsd cfg.osd1; };
+    osd2 = generateHost { pkgs = pkgs; cephConfig = cephConfigOsd cfg.osd2; networkConfig = networkOsd cfg.osd2; };
+  };
+
+  testScript = testscript;
+})
diff --git a/nixpkgs/nixos/tests/ceph-single-node-bluestore.nix b/nixpkgs/nixos/tests/ceph-single-node-bluestore.nix
new file mode 100644
index 000000000000..8bd1a78244a2
--- /dev/null
+++ b/nixpkgs/nixos/tests/ceph-single-node-bluestore.nix
@@ -0,0 +1,204 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+
+let
+  cfg = {
+    clusterId = "066ae264-2a5d-4729-8001-6ad265f50b03";
+    monA = {
+      name = "a";
+      ip = "192.168.1.1";
+    };
+    osd0 = {
+      name = "0";
+      key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
+      uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
+    };
+    osd1 = {
+      name = "1";
+      key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
+      uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
+    };
+    osd2 = {
+      name = "2";
+      key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
+      uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
+    };
+  };
+  generateCephConfig = { daemonConfig }: {
+    enable = true;
+    global = {
+      fsid = cfg.clusterId;
+      monHost = cfg.monA.ip;
+      monInitialMembers = cfg.monA.name;
+    };
+  } // daemonConfig;
+
+  generateHost = { pkgs, cephConfig, networkConfig, ... }: {
+    virtualisation = {
+      emptyDiskImages = [ 20480 20480 20480 ];
+      vlans = [ 1 ];
+    };
+
+    networking = networkConfig;
+
+    environment.systemPackages = with pkgs; [
+      bash
+      sudo
+      ceph
+      xfsprogs
+    ];
+
+    boot.kernelModules = [ "xfs" ];
+
+    services.ceph = cephConfig;
+  };
+
+  networkMonA = {
+    dhcpcd.enable = false;
+    interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+      { address = cfg.monA.ip; prefixLength = 24; }
+    ];
+  };
+  cephConfigMonA = generateCephConfig { daemonConfig = {
+    mon = {
+      enable = true;
+      daemons = [ cfg.monA.name ];
+    };
+    mgr = {
+      enable = true;
+      daemons = [ cfg.monA.name ];
+    };
+    osd = {
+      enable = true;
+      daemons = [ cfg.osd0.name cfg.osd1.name cfg.osd2.name ];
+    };
+  }; };
+
+  # Following deployment is based on the manual deployment described here:
+  # https://docs.ceph.com/docs/master/install/manual-deployment/
+  # For other ways to deploy a ceph cluster, look at the documentation at
+  # https://docs.ceph.com/docs/master/
+  testscript = { ... }: ''
+    start_all()
+
+    monA.wait_for_unit("network.target")
+
+    # Bootstrap ceph-mon daemon
+    monA.succeed(
+        "sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
+        "sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
+        "sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
+        "monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
+        "sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
+        "sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
+        "systemctl start ceph-mon-${cfg.monA.name}",
+    )
+    monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
+    monA.succeed("ceph mon enable-msgr2")
+    monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
+
+    # Can't check ceph status until a mon is up
+    monA.succeed("ceph -s | grep 'mon: 1 daemons'")
+
+    # Start the ceph-mgr daemon, after copying in the keyring
+    monA.succeed(
+        "sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
+        "ceph auth get-or-create mgr.${cfg.monA.name} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-${cfg.monA.name}/keyring",
+        "systemctl start ceph-mgr-${cfg.monA.name}",
+    )
+    monA.wait_for_unit("ceph-mgr-a")
+    monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+
+    # Bootstrap OSDs
+    monA.succeed(
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
+        "echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd0.name}/type",
+        "ln -sf /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}/block",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
+        "echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd1.name}/type",
+        "ln -sf /dev/vdc /var/lib/ceph/osd/ceph-${cfg.osd1.name}/block",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
+        "echo bluestore > /var/lib/ceph/osd/ceph-${cfg.osd2.name}/type",
+        "ln -sf /dev/vdd /var/lib/ceph/osd/ceph-${cfg.osd2.name}/block",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
+        'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
+        'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
+        'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
+    )
+
+    # Initialize the OSDs with regular filestore
+    monA.succeed(
+        "ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
+        "ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
+        "ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
+        "chown -R ceph:ceph /var/lib/ceph/osd",
+        "systemctl start ceph-osd-${cfg.osd0.name}",
+        "systemctl start ceph-osd-${cfg.osd1.name}",
+        "systemctl start ceph-osd-${cfg.osd2.name}",
+    )
+    monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+
+    monA.succeed(
+        "ceph osd pool create single-node-test 32 32",
+        "ceph osd pool ls | grep 'single-node-test'",
+
+        # We need to enable an application on the pool, otherwise it will
+        # stay unhealthy in state POOL_APP_NOT_ENABLED.
+        # Creating a CephFS would do this automatically, but we haven't done that here.
+        # See: https://docs.ceph.com/en/reef/rados/operations/pools/#associating-a-pool-with-an-application
+        # We use the custom application name "nixos-test" for this.
+        "ceph osd pool application enable single-node-test nixos-test",
+
+        "ceph osd pool rename single-node-test single-node-other-test",
+        "ceph osd pool ls | grep 'single-node-other-test'",
+    )
+    monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
+    monA.succeed(
+        "ceph osd getcrushmap -o crush",
+        "crushtool -d crush -o decrushed",
+        "sed 's/step chooseleaf firstn 0 type host/step chooseleaf firstn 0 type osd/' decrushed > modcrush",
+        "crushtool -c modcrush -o recrushed",
+        "ceph osd setcrushmap -i recrushed",
+        "ceph osd pool set single-node-other-test size 2",
+    )
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+    monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
+    monA.fail(
+        "ceph osd pool ls | grep 'multi-node-test'",
+        "ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it",
+    )
+
+    # Shut down ceph by stopping ceph.target.
+    monA.succeed("systemctl stop ceph.target")
+
+    # Start it up
+    monA.succeed("systemctl start ceph.target")
+    monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
+    monA.wait_for_unit("ceph-mgr-${cfg.monA.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd0.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd1.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd2.name}")
+
+    # Ensure the cluster comes back up again
+    monA.succeed("ceph -s | grep 'mon: 1 daemons'")
+    monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
+    monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+  '';
+in {
+  name = "basic-single-node-ceph-cluster-bluestore";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lukegb ];
+  };
+
+  nodes = {
+    monA = generateHost { pkgs = pkgs; cephConfig = cephConfigMonA; networkConfig = networkMonA; };
+  };
+
+  testScript = testscript;
+})
diff --git a/nixpkgs/nixos/tests/ceph-single-node.nix b/nixpkgs/nixos/tests/ceph-single-node.nix
new file mode 100644
index 000000000000..c34ec511dc6d
--- /dev/null
+++ b/nixpkgs/nixos/tests/ceph-single-node.nix
@@ -0,0 +1,215 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+
+let
+  cfg = {
+    clusterId = "066ae264-2a5d-4729-8001-6ad265f50b03";
+    monA = {
+      name = "a";
+      ip = "192.168.1.1";
+    };
+    osd0 = {
+      name = "0";
+      key = "AQBCEJNa3s8nHRAANvdsr93KqzBznuIWm2gOGg==";
+      uuid = "55ba2294-3e24-478f-bee0-9dca4c231dd9";
+    };
+    osd1 = {
+      name = "1";
+      key = "AQBEEJNac00kExAAXEgy943BGyOpVH1LLlHafQ==";
+      uuid = "5e97a838-85b6-43b0-8950-cb56d554d1e5";
+    };
+    osd2 = {
+      name = "2";
+      key = "AQAdyhZeIaUlARAAGRoidDAmS6Vkp546UFEf5w==";
+      uuid = "ea999274-13d0-4dd5-9af9-ad25a324f72f";
+    };
+  };
+  generateCephConfig = { daemonConfig }: {
+    enable = true;
+    global = {
+      fsid = cfg.clusterId;
+      monHost = cfg.monA.ip;
+      monInitialMembers = cfg.monA.name;
+    };
+  } // daemonConfig;
+
+  generateHost = { pkgs, cephConfig, networkConfig, ... }: {
+    virtualisation = {
+      emptyDiskImages = [ 20480 20480 20480 ];
+      vlans = [ 1 ];
+    };
+
+    networking = networkConfig;
+
+    environment.systemPackages = with pkgs; [
+      bash
+      sudo
+      ceph
+      xfsprogs
+    ];
+
+    boot.kernelModules = [ "xfs" ];
+
+    services.ceph = cephConfig;
+  };
+
+  networkMonA = {
+    dhcpcd.enable = false;
+    interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+      { address = cfg.monA.ip; prefixLength = 24; }
+    ];
+  };
+  cephConfigMonA = generateCephConfig { daemonConfig = {
+    mon = {
+      enable = true;
+      daemons = [ cfg.monA.name ];
+    };
+    mgr = {
+      enable = true;
+      daemons = [ cfg.monA.name ];
+    };
+    osd = {
+      enable = true;
+      daemons = [ cfg.osd0.name cfg.osd1.name cfg.osd2.name ];
+    };
+  }; };
+
+  # Following deployment is based on the manual deployment described here:
+  # https://docs.ceph.com/docs/master/install/manual-deployment/
+  # For other ways to deploy a ceph cluster, look at the documentation at
+  # https://docs.ceph.com/docs/master/
+  testscript = { ... }: ''
+    start_all()
+
+    monA.wait_for_unit("network.target")
+
+    # Bootstrap ceph-mon daemon
+    monA.succeed(
+        "sudo -u ceph ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'",
+        "sudo -u ceph ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'",
+        "sudo -u ceph ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring",
+        "monmaptool --create --add ${cfg.monA.name} ${cfg.monA.ip} --fsid ${cfg.clusterId} /tmp/monmap",
+        "sudo -u ceph ceph-mon --mkfs -i ${cfg.monA.name} --monmap /tmp/monmap --keyring /tmp/ceph.mon.keyring",
+        "sudo -u ceph touch /var/lib/ceph/mon/ceph-${cfg.monA.name}/done",
+        "systemctl start ceph-mon-${cfg.monA.name}",
+    )
+    monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
+    monA.succeed("ceph mon enable-msgr2")
+    monA.succeed("ceph config set mon auth_allow_insecure_global_id_reclaim false")
+
+    # Can't check ceph status until a mon is up
+    monA.succeed("ceph -s | grep 'mon: 1 daemons'")
+
+    # Start the ceph-mgr daemon, after copying in the keyring
+    monA.succeed(
+        "sudo -u ceph mkdir -p /var/lib/ceph/mgr/ceph-${cfg.monA.name}/",
+        "ceph auth get-or-create mgr.${cfg.monA.name} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-${cfg.monA.name}/keyring",
+        "systemctl start ceph-mgr-${cfg.monA.name}",
+    )
+    monA.wait_for_unit("ceph-mgr-a")
+    monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+
+    # Bootstrap OSDs
+    monA.succeed(
+        "mkfs.xfs /dev/vdb",
+        "mkfs.xfs /dev/vdc",
+        "mkfs.xfs /dev/vdd",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
+        "mount /dev/vdb /var/lib/ceph/osd/ceph-${cfg.osd0.name}",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
+        "mount /dev/vdc /var/lib/ceph/osd/ceph-${cfg.osd1.name}",
+        "mkdir -p /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
+        "mount /dev/vdd /var/lib/ceph/osd/ceph-${cfg.osd2.name}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd0.name}/keyring --name osd.${cfg.osd0.name} --add-key ${cfg.osd0.key}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd1.name}/keyring --name osd.${cfg.osd1.name} --add-key ${cfg.osd1.key}",
+        "ceph-authtool --create-keyring /var/lib/ceph/osd/ceph-${cfg.osd2.name}/keyring --name osd.${cfg.osd2.name} --add-key ${cfg.osd2.key}",
+        'echo \'{"cephx_secret": "${cfg.osd0.key}"}\' | ceph osd new ${cfg.osd0.uuid} -i -',
+        'echo \'{"cephx_secret": "${cfg.osd1.key}"}\' | ceph osd new ${cfg.osd1.uuid} -i -',
+        'echo \'{"cephx_secret": "${cfg.osd2.key}"}\' | ceph osd new ${cfg.osd2.uuid} -i -',
+    )
+
+    # Initialize the OSDs with regular filestore
+    monA.succeed(
+        "ceph-osd -i ${cfg.osd0.name} --mkfs --osd-uuid ${cfg.osd0.uuid}",
+        "ceph-osd -i ${cfg.osd1.name} --mkfs --osd-uuid ${cfg.osd1.uuid}",
+        "ceph-osd -i ${cfg.osd2.name} --mkfs --osd-uuid ${cfg.osd2.uuid}",
+        "chown -R ceph:ceph /var/lib/ceph/osd",
+        "systemctl start ceph-osd-${cfg.osd0.name}",
+        "systemctl start ceph-osd-${cfg.osd1.name}",
+        "systemctl start ceph-osd-${cfg.osd2.name}",
+    )
+    monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+
+    monA.succeed(
+        "ceph osd pool create single-node-test 32 32",
+        "ceph osd pool ls | grep 'single-node-test'",
+
+        # We need to enable an application on the pool, otherwise it will
+        # stay unhealthy in state POOL_APP_NOT_ENABLED.
+        # Creating a CephFS would do this automatically, but we haven't done that here.
+        # See: https://docs.ceph.com/en/reef/rados/operations/pools/#associating-a-pool-with-an-application
+        # We use the custom application name "nixos-test" for this.
+        "ceph osd pool application enable single-node-test nixos-test",
+
+        "ceph osd pool rename single-node-test single-node-other-test",
+        "ceph osd pool ls | grep 'single-node-other-test'",
+    )
+    monA.wait_until_succeeds("ceph -s | grep '2 pools, 33 pgs'")
+    monA.succeed(
+        "ceph osd getcrushmap -o crush",
+        "crushtool -d crush -o decrushed",
+        "sed 's/step chooseleaf firstn 0 type host/step chooseleaf firstn 0 type osd/' decrushed > modcrush",
+        "crushtool -c modcrush -o recrushed",
+        "ceph osd setcrushmap -i recrushed",
+        "ceph osd pool set single-node-other-test size 2",
+    )
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+    monA.wait_until_succeeds("ceph -s | grep '33 active+clean'")
+    monA.fail(
+        "ceph osd pool ls | grep 'multi-node-test'",
+        "ceph osd pool delete single-node-other-test single-node-other-test --yes-i-really-really-mean-it",
+    )
+
+    # Shut down ceph by stopping ceph.target.
+    monA.succeed("systemctl stop ceph.target")
+
+    # Start it up
+    monA.succeed("systemctl start ceph.target")
+    monA.wait_for_unit("ceph-mon-${cfg.monA.name}")
+    monA.wait_for_unit("ceph-mgr-${cfg.monA.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd0.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd1.name}")
+    monA.wait_for_unit("ceph-osd-${cfg.osd2.name}")
+
+    # Ensure the cluster comes back up again
+    monA.succeed("ceph -s | grep 'mon: 1 daemons'")
+    monA.wait_until_succeeds("ceph -s | grep 'quorum ${cfg.monA.name}'")
+    monA.wait_until_succeeds("ceph osd stat | grep -e '3 osds: 3 up[^,]*, 3 in'")
+    monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+
+    # Enable the dashboard and recheck health
+    monA.succeed(
+        "ceph mgr module enable dashboard",
+        "ceph config set mgr mgr/dashboard/ssl false",
+        # default is 8080 but it's better to be explicit
+        "ceph config set mgr mgr/dashboard/server_port 8080",
+    )
+    monA.wait_for_open_port(8080)
+    monA.wait_until_succeeds("curl -q --fail http://localhost:8080")
+    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+  '';
+in {
+  name = "basic-single-node-ceph-cluster";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lejonet johanot ];
+  };
+
+  nodes = {
+    monA = generateHost { pkgs = pkgs; cephConfig = cephConfigMonA; networkConfig = networkMonA; };
+  };
+
+  testScript = testscript;
+})
diff --git a/nixpkgs/nixos/tests/certmgr.nix b/nixpkgs/nixos/tests/certmgr.nix
new file mode 100644
index 000000000000..8f5b89487793
--- /dev/null
+++ b/nixpkgs/nixos/tests/certmgr.nix
@@ -0,0 +1,155 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+let
+  mkSpec = { host, service ? null, action }: {
+    inherit action;
+    authority = {
+      file = {
+        group = "nginx";
+        owner = "nginx";
+        path = "/var/ssl/${host}-ca.pem";
+      };
+      label = "www_ca";
+      profile = "three-month";
+      remote = "localhost:8888";
+    };
+    certificate = {
+      group = "nginx";
+      owner = "nginx";
+      path = "/var/ssl/${host}-cert.pem";
+    };
+    private_key = {
+      group = "nginx";
+      mode = "0600";
+      owner = "nginx";
+      path = "/var/ssl/${host}-key.pem";
+    };
+    request = {
+      CN = host;
+      hosts = [ host "www.${host}" ];
+      key = {
+        algo = "rsa";
+        size = 2048;
+      };
+      names = [
+        {
+          C = "US";
+          L = "San Francisco";
+          O = "Example, LLC";
+          ST = "CA";
+        }
+      ];
+    };
+    inherit service;
+  };
+
+  mkCertmgrTest = { svcManager, specs, testScript }: makeTest {
+    name = "certmgr-" + svcManager;
+    nodes = {
+      machine = { config, lib, pkgs, ... }: {
+        networking.firewall.allowedTCPPorts = with config.services; [ cfssl.port certmgr.metricsPort ];
+        networking.extraHosts = "127.0.0.1 imp.example.org decl.example.org";
+
+        services.cfssl.enable = true;
+        systemd.services.cfssl.after = [ "cfssl-init.service" "networking.target" ];
+
+        systemd.tmpfiles.rules = [ "d /var/ssl 777 root root" ];
+
+        systemd.services.cfssl-init = {
+          description = "Initialize the cfssl CA";
+          wantedBy    = [ "multi-user.target" ];
+          serviceConfig = {
+            User             = "cfssl";
+            Type             = "oneshot";
+            WorkingDirectory = config.services.cfssl.dataDir;
+          };
+          script = ''
+            ${pkgs.cfssl}/bin/cfssl genkey -initca ${pkgs.writeText "ca.json" (builtins.toJSON {
+              hosts = [ "ca.example.com" ];
+              key = {
+                algo = "rsa"; size = 4096; };
+                names = [
+                  {
+                    C = "US";
+                    L = "San Francisco";
+                    O = "Internet Widgets, LLC";
+                    OU = "Certificate Authority";
+                    ST = "California";
+                  }
+                ];
+            })} | ${pkgs.cfssl}/bin/cfssljson -bare ca
+          '';
+        };
+
+        services.nginx = {
+          enable = true;
+          virtualHosts = lib.mkMerge (map (host: {
+            ${host} = {
+              sslCertificate = "/var/ssl/${host}-cert.pem";
+              sslCertificateKey = "/var/ssl/${host}-key.pem";
+              extraConfig = ''
+                ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+              '';
+              onlySSL = true;
+              serverName = host;
+              root = pkgs.writeTextDir "index.html" "It works!";
+            };
+          }) [ "imp.example.org" "decl.example.org" ]);
+        };
+
+        systemd.services.nginx.wantedBy = lib.mkForce [];
+
+        systemd.services.certmgr.after = [ "cfssl.service" ];
+        services.certmgr = {
+          enable = true;
+          inherit svcManager;
+          inherit specs;
+        };
+
+      };
+    };
+    inherit testScript;
+  };
+in
+{
+  systemd = mkCertmgrTest {
+    svcManager = "systemd";
+    specs = {
+      decl = mkSpec { host = "decl.example.org"; service = "nginx"; action ="restart"; };
+      imp = toString (pkgs.writeText "test.json" (builtins.toJSON (
+        mkSpec { host = "imp.example.org"; service = "nginx"; action = "restart"; }
+      )));
+    };
+    testScript = ''
+      machine.wait_for_unit("cfssl.service")
+      machine.wait_until_succeeds("ls /var/ssl/decl.example.org-ca.pem")
+      machine.wait_until_succeeds("ls /var/ssl/decl.example.org-key.pem")
+      machine.wait_until_succeeds("ls /var/ssl/decl.example.org-cert.pem")
+      machine.wait_until_succeeds("ls /var/ssl/imp.example.org-ca.pem")
+      machine.wait_until_succeeds("ls /var/ssl/imp.example.org-key.pem")
+      machine.wait_until_succeeds("ls /var/ssl/imp.example.org-cert.pem")
+      machine.wait_for_unit("nginx.service")
+      assert 1 < int(machine.succeed('journalctl -u nginx | grep "Starting Nginx" | wc -l'))
+      machine.succeed("curl --cacert /var/ssl/imp.example.org-ca.pem https://imp.example.org")
+      machine.succeed(
+          "curl --cacert /var/ssl/decl.example.org-ca.pem https://decl.example.org"
+      )
+    '';
+  };
+
+  command = mkCertmgrTest {
+    svcManager = "command";
+    specs = {
+      test = mkSpec { host = "command.example.org"; action = "touch /tmp/command.executed"; };
+    };
+    testScript = ''
+      machine.wait_for_unit("cfssl.service")
+      machine.wait_until_succeeds("stat /tmp/command.executed")
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/tests/cfssl.nix b/nixpkgs/nixos/tests/cfssl.nix
new file mode 100644
index 000000000000..e673df3131f8
--- /dev/null
+++ b/nixpkgs/nixos/tests/cfssl.nix
@@ -0,0 +1,67 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "cfssl";
+
+  nodes.machine = { config, lib, pkgs, ... }:
+  {
+    networking.firewall.allowedTCPPorts = [ config.services.cfssl.port ];
+
+    services.cfssl.enable = true;
+    systemd.services.cfssl.after = [ "cfssl-init.service" ];
+
+    systemd.services.cfssl-init = {
+      description = "Initialize the cfssl CA";
+      wantedBy    = [ "multi-user.target" ];
+      serviceConfig = {
+        User             = "cfssl";
+        Type             = "oneshot";
+        WorkingDirectory = config.services.cfssl.dataDir;
+      };
+      script = with pkgs; ''
+        ${cfssl}/bin/cfssl genkey -initca ${pkgs.writeText "ca.json" (builtins.toJSON {
+          hosts = [ "ca.example.com" ];
+          key = {
+            algo = "rsa"; size = 4096; };
+            names = [
+              {
+                C = "US";
+                L = "San Francisco";
+                O = "Internet Widgets, LLC";
+                OU = "Certificate Authority";
+                ST = "California";
+              }
+            ];
+        })} | ${cfssl}/bin/cfssljson -bare ca
+      '';
+    };
+  };
+
+  testScript =
+  let
+    cfsslrequest = with pkgs; writeScript "cfsslrequest" ''
+      curl -f -X POST -H "Content-Type: application/json" -d @${csr} \
+        http://localhost:8888/api/v1/cfssl/newkey | ${cfssl}/bin/cfssljson /tmp/certificate
+    '';
+    csr = pkgs.writeText "csr.json" (builtins.toJSON {
+      CN = "www.example.com";
+      hosts = [ "example.com" "www.example.com" ];
+      key = {
+        algo = "rsa";
+        size = 2048;
+      };
+      names = [
+        {
+          C = "US";
+          L = "San Francisco";
+          O = "Example Company, LLC";
+          OU = "Operations";
+          ST = "California";
+        }
+      ];
+    });
+  in
+    ''
+      machine.wait_for_unit("cfssl.service")
+      machine.wait_until_succeeds("${cfsslrequest}")
+      machine.succeed("ls /tmp/certificate-key.pem")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/cgit.nix b/nixpkgs/nixos/tests/cgit.nix
new file mode 100644
index 000000000000..6aed06adefdf
--- /dev/null
+++ b/nixpkgs/nixos/tests/cgit.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  robotsTxt = pkgs.writeText "cgit-robots.txt" ''
+    User-agent: *
+    Disallow: /
+  '';
+in {
+  name = "cgit";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ schnusch ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      services.cgit."localhost" = {
+        enable = true;
+        package = pkgs.cgit.overrideAttrs ({ postInstall, ... }: {
+          postInstall = ''
+            ${postInstall}
+            cp ${robotsTxt} "$out/cgit/robots.txt"
+          '';
+        });
+        nginx.location = "/(c)git/";
+        repos = {
+          some-repo = {
+            path = "/srv/git/some-repo";
+            desc = "some-repo description";
+          };
+        };
+      };
+
+      environment.systemPackages = [ pkgs.git ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("network.target")
+    server.wait_for_open_port(80)
+
+    server.succeed("curl -fsS http://localhost/%28c%29git/cgit.css")
+
+    server.succeed("curl -fsS http://localhost/%28c%29git/robots.txt | diff -u - ${robotsTxt}")
+
+    server.succeed(
+        "curl -fsS http://localhost/%28c%29git/ | grep -F 'some-repo description'"
+    )
+
+    server.fail("curl -fsS http://localhost/robots.txt")
+
+    server.succeed("${pkgs.writeShellScript "setup-cgit-test-repo" ''
+      set -e
+      git init --bare -b master /srv/git/some-repo
+      git init -b master reference
+      cd reference
+      git remote add origin /srv/git/some-repo
+      date > date.txt
+      git add date.txt
+      git -c user.name=test -c user.email=test@localhost commit -m 'add date'
+      git push -u origin master
+    ''}")
+
+    server.succeed(
+        "curl -fsS 'http://localhost/%28c%29git/some-repo/plain/date.txt?id=master' | diff -u reference/date.txt -"
+    )
+
+    server.succeed(
+       "git clone http://localhost/%28c%29git/some-repo && diff -u reference/date.txt some-repo/date.txt"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/charliecloud.nix b/nixpkgs/nixos/tests/charliecloud.nix
new file mode 100644
index 000000000000..28c3e2f2dbf7
--- /dev/null
+++ b/nixpkgs/nixos/tests/charliecloud.nix
@@ -0,0 +1,43 @@
+# This test checks charliecloud image construction and run
+
+import ./make-test-python.nix ({ pkgs, ...} : let
+
+  dockerfile = pkgs.writeText "Dockerfile" ''
+    FROM nix
+    RUN mkdir /home /tmp
+    RUN touch /etc/passwd /etc/group
+    CMD ["true"]
+  '';
+
+in {
+  name = "charliecloud";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ bzizou ];
+  };
+
+  nodes = {
+    host = { ... }: {
+      environment.systemPackages = [ pkgs.charliecloud ];
+      virtualisation.docker.enable = true;
+      users.users.alice = {
+        isNormalUser = true;
+        extraGroups = [ "docker" ];
+      };
+    };
+  };
+
+  testScript = ''
+    host.start()
+    host.wait_for_unit("docker.service")
+    host.succeed(
+        'su - alice -c "docker load --input=${pkgs.dockerTools.examples.nix}"'
+    )
+    host.succeed(
+        "cp ${dockerfile} /home/alice/Dockerfile"
+    )
+    host.succeed('su - alice -c "ch-build -t hello ."')
+    host.succeed('su - alice -c "ch-builder2tar hello /var/tmp"')
+    host.succeed('su - alice -c "ch-tar2dir /var/tmp/hello.tar.gz /var/tmp"')
+    host.succeed('su - alice -c "ch-run /var/tmp/hello -- echo Running_From_Container_OK"')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/chromium.nix b/nixpkgs/nixos/tests/chromium.nix
new file mode 100644
index 000000000000..cdfdcc9bcdd2
--- /dev/null
+++ b/nixpkgs/nixos/tests/chromium.nix
@@ -0,0 +1,269 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, channelMap ? { # Maps "channels" to packages
+    stable        = pkgs.chromium;
+    beta          = pkgs.chromiumBeta;
+    dev           = pkgs.chromiumDev;
+    ungoogled     = pkgs.ungoogled-chromium;
+    chrome-stable = pkgs.google-chrome;
+    chrome-beta   = pkgs.google-chrome-beta;
+    chrome-dev    = pkgs.google-chrome-dev;
+  }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  user = "alice";
+
+  startupHTML = pkgs.writeText "chromium-startup.html" ''
+    <!DOCTYPE html>
+    <html>
+    <head>
+    <meta charset="UTF-8">
+    <title>Chromium startup notifier</title>
+    </head>
+    <body onload="javascript:document.title='startup done'">
+      <img src="file://${pkgs.fetchurl {
+        url = "https://nixos.org/logo/nixos-hex.svg";
+        sha256 = "07ymq6nw8kc22m7kzxjxldhiq8gzmc7f45kq2bvhbdm0w5s112s4";
+      }}" />
+    </body>
+    </html>
+  '';
+in
+
+mapAttrs (channel: chromiumPkg: makeTest {
+  name = "chromium-${channel}";
+  meta = {
+    maintainers = with maintainers; [ aszlig primeos ];
+  } // optionalAttrs (chromiumPkg.meta ? timeout) {
+    # https://github.com/NixOS/hydra/issues/591#issuecomment-435125621
+    # Note: optionalAttrs is used since meta.timeout is not set for Google Chrome
+    inherit (chromiumPkg.meta) timeout;
+  };
+
+  enableOCR = true;
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ./common/x11.nix ];
+    virtualisation.memorySize = 2047;
+    test-support.displayManager.auto.user = user;
+    environment = {
+      systemPackages = [ chromiumPkg ];
+      variables."XAUTHORITY" = "/home/alice/.Xauthority";
+    };
+  };
+
+  testScript = let
+    xdo = name: text: let
+      xdoScript = pkgs.writeText "${name}.xdo" text;
+    in "${pkgs.xdotool}/bin/xdotool ${xdoScript}";
+  in ''
+    import shlex
+    import re
+    from contextlib import contextmanager
+
+
+    major_version = "${versions.major (getVersion chromiumPkg.name)}"
+
+
+    # Run as user alice
+    def ru(cmd):
+        return "su - ${user} -c " + shlex.quote(cmd)
+
+
+    def launch_browser():
+        """Launches the web browser with the correct options."""
+        # Determine the name of the binary:
+        pname = "${getName chromiumPkg.name}"
+        if pname.find("chromium") != -1:
+            binary = "chromium"  # Same name for all channels and ungoogled-chromium
+        elif pname == "google-chrome":
+            binary = "google-chrome-stable"
+        elif pname == "google-chrome-dev":
+            binary = "google-chrome-unstable"
+        else:  # For google-chrome-beta and as fallback:
+            binary = pname
+        # Add optional CLI options:
+        options = []
+        if major_version > "95" and not pname.startswith("google-chrome"):
+            # Workaround to avoid a GPU crash:
+            options.append("--use-gl=swiftshader")
+        # Launch the process:
+        options.append("file://${startupHTML}")
+        machine.succeed(ru(f'ulimit -c unlimited; {binary} {shlex.join(options)} >&2 & disown'))
+        if binary.startswith("google-chrome"):
+            # Need to click away the first window:
+            machine.wait_for_text("Make Google Chrome the default browser")
+            machine.screenshot("google_chrome_default_browser_prompt")
+            machine.send_key("ret")
+
+
+    def create_new_win():
+        """Creates a new Chromium window."""
+        with machine.nested("Creating a new Chromium window"):
+            machine.wait_until_succeeds(
+                ru(
+                    "${xdo "create_new_win-select_main_window" ''
+                      search --onlyvisible --name "startup done"
+                      windowfocus --sync
+                      windowactivate --sync
+                    ''}"
+                )
+            )
+            machine.send_key("ctrl-n")
+            # Wait until the new window appears:
+            machine.wait_until_succeeds(
+                ru(
+                    "${xdo "create_new_win-wait_for_window" ''
+                      search --onlyvisible --name "New Tab"
+                      windowfocus --sync
+                      windowactivate --sync
+                    ''}"
+                )
+            )
+
+
+    def close_new_tab_win():
+        """Closes the Chromium window with the title "New Tab"."""
+        machine.wait_until_succeeds(
+            ru(
+                "${xdo "close_new_tab_win-select_main_window" ''
+                  search --onlyvisible --name "New Tab"
+                  windowfocus --sync
+                  windowactivate --sync
+                ''}"
+            )
+        )
+        machine.send_key("ctrl-w")
+        # Wait until the closed window disappears:
+        machine.wait_until_fails(
+            ru(
+                "${xdo "close_new_tab_win-wait_for_close" ''
+                  search --onlyvisible --name "New Tab"
+                ''}"
+            )
+        )
+
+
+    @contextmanager
+    def test_new_win(description, url, window_name):
+        create_new_win()
+        machine.wait_for_window("New Tab")
+        machine.send_chars(f"{url}\n")
+        machine.wait_for_window(window_name)
+        machine.screenshot(description)
+        machine.succeed(
+            ru(
+                "${xdo "copy-all" ''
+                  key --delay 1000 Ctrl+a Ctrl+c
+                ''}"
+            )
+        )
+        clipboard = machine.succeed(
+            ru("${pkgs.xclip}/bin/xclip -o")
+        )
+        if url == "chrome://gpu":
+            clipboard = ""  # TODO: We cannot copy the text via Ctrl+a
+        print(f"{description} window content:\n{clipboard}")
+        with machine.nested(description):
+            yield clipboard
+        # Close the newly created window:
+        machine.send_key("ctrl-w")
+
+
+    machine.wait_for_x()
+
+    launch_browser()
+
+    machine.wait_for_text("startup done")
+    machine.wait_until_succeeds(
+        ru(
+            "${xdo "check-startup" ''
+              search --sync --onlyvisible --name "startup done"
+              # close first start help popup
+              key -delay 1000 Escape
+              windowfocus --sync
+              windowactivate --sync
+            ''}"
+        )
+    )
+
+    create_new_win()
+    # Optional: Wait for the new tab page to fully load before taking the screenshot:
+    machine.wait_for_text("Web Store")
+    machine.screenshot("empty_windows")
+    close_new_tab_win()
+
+    machine.screenshot("startup_done")
+
+    with test_new_win("sandbox_info", "chrome://sandbox", "Sandbox Status") as clipboard:
+        filters = [
+            "layer 1 sandbox.*namespace",
+            "pid namespaces.*yes",
+            "network namespaces.*yes",
+            "seccomp.*sandbox.*yes",
+            "you are adequately sandboxed",
+        ]
+        if not all(
+            re.search(filter, clipboard, flags=re.DOTALL | re.IGNORECASE)
+            for filter in filters
+        ):
+            assert False, f"sandbox not working properly: {clipboard}"
+
+        machine.sleep(1)
+        machine.succeed(
+            ru(
+                "${xdo "find-window-after-copy" ''
+                  search --onlyvisible --name "Sandbox Status"
+                ''}"
+            )
+        )
+
+        clipboard = machine.succeed(
+            ru(
+                "echo void | ${pkgs.xclip}/bin/xclip -i >&2"
+            )
+        )
+        machine.succeed(
+            ru(
+                "${xdo "copy-sandbox-info" ''
+                  key --delay 1000 Ctrl+a Ctrl+c
+                ''}"
+            )
+        )
+
+        clipboard = machine.succeed(
+            ru("${pkgs.xclip}/bin/xclip -o")
+        )
+        if not all(
+            re.search(filter, clipboard, flags=re.DOTALL | re.IGNORECASE)
+            for filter in filters
+        ):
+            assert False, f"copying twice in a row does not work properly: {clipboard}"
+
+        machine.screenshot("after_copy_from_chromium")
+
+
+    with test_new_win("gpu_info", "chrome://gpu", "GPU Internals"):
+        # To check the text rendering (catches regressions like #131074):
+        machine.wait_for_text("Graphics Feature Status")
+        # TODO: Fix copying all of the text to the clipboard
+
+
+    with test_new_win("version_info", "chrome://version", "About Version") as clipboard:
+        filters = [
+            r"${chromiumPkg.version} \(Official Build",
+        ]
+        if not all(
+            re.search(filter, clipboard) for filter in filters
+        ):
+            assert False, "Version info not correct."
+
+
+    machine.shutdown()
+  '';
+}) channelMap
diff --git a/nixpkgs/nixos/tests/chrony-ptp.nix b/nixpkgs/nixos/tests/chrony-ptp.nix
new file mode 100644
index 000000000000..b2634a8cfc5c
--- /dev/null
+++ b/nixpkgs/nixos/tests/chrony-ptp.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "chrony-ptp";
+
+  meta = {
+    maintainers = with lib.maintainers; [ gkleen ];
+  };
+
+  nodes = {
+    qemuGuest = { lib, ... }: {
+      boot.kernelModules = [ "ptp_kvm" ];
+
+      services.chrony = {
+        enable = true;
+        extraConfig = ''
+          refclock PHC /dev/ptp_kvm poll 2 dpoll -2 offset 0 stratum 3
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    qemuGuest.wait_for_unit('multi-user.target')
+    qemuGuest.succeed('systemctl is-active chronyd.service')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/chrony.nix b/nixpkgs/nixos/tests/chrony.nix
new file mode 100644
index 000000000000..578b1e32d50c
--- /dev/null
+++ b/nixpkgs/nixos/tests/chrony.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "chrony";
+
+  meta = {
+    maintainers = with lib.maintainers; [ fpletz ];
+  };
+
+  nodes = {
+    default = {
+      services.chrony.enable = true;
+    };
+    graphene-hardened = {
+      services.chrony.enable = true;
+      services.chrony.enableMemoryLocking = true;
+      environment.memoryAllocator.provider = "graphene-hardened";
+      # dhcpcd privsep is incompatible with graphene-hardened
+      networking.useNetworkd = true;
+    };
+  };
+
+  testScript = {nodes, ...} : let
+    graphene-hardened = nodes.graphene-hardened.system.build.toplevel;
+  in ''
+    default.start()
+    default.wait_for_unit('multi-user.target')
+    default.succeed('systemctl is-active chronyd.service')
+    default.succeed('${graphene-hardened}/bin/switch-to-configuration test')
+    default.succeed('systemctl is-active chronyd.service')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/cinnamon-wayland.nix b/nixpkgs/nixos/tests/cinnamon-wayland.nix
new file mode 100644
index 000000000000..1629ead16f41
--- /dev/null
+++ b/nixpkgs/nixos/tests/cinnamon-wayland.nix
@@ -0,0 +1,77 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "cinnamon-wayland";
+
+  meta.maintainers = lib.teams.cinnamon.members;
+
+  nodes.machine = { nodes, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.desktopManager.cinnamon.enable = true;
+    services.xserver.displayManager = {
+      autoLogin.enable = true;
+      autoLogin.user = nodes.machine.users.users.alice.name;
+      defaultSession = "cinnamon-wayland";
+    };
+
+    # For the sessionPath subtest.
+    services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gnome.gpaste ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
+      su = command: "su - ${user.name} -c '${env} ${command}'";
+
+      # Call javascript in cinnamon (the shell), returns a tuple (success, output),
+      # where `success` is true if the dbus call was successful and `output` is what
+      # the javascript evaluates to.
+      eval = name: su "gdbus call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval ${name}";
+    in
+    ''
+      machine.wait_for_unit("display-manager.service")
+
+      with subtest("Wait for wayland server"):
+          machine.wait_for_file("/run/user/${toString user.uid}/wayland-0")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Wait for the Cinnamon shell"):
+          # Correct output should be (true, '2')
+          # https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
+          machine.wait_until_succeeds("${eval "Main.runState"} | grep -q 'true,..2'")
+
+      with subtest("Check if Cinnamon components actually start"):
+          for i in ["csd-media-keys", "xapp-sn-watcher", "nemo-desktop"]:
+            machine.wait_until_succeeds(f"pgrep -f {i}")
+          machine.wait_until_succeeds("journalctl -b --grep 'Loaded applet menu@cinnamon.org'")
+          machine.wait_until_succeeds("journalctl -b --grep 'calendar@cinnamon.org: Calendar events supported'")
+
+      with subtest("Check if sessionPath option actually works"):
+          machine.succeed("${eval "imports.gi.GIRepository.Repository.get_search_path\\(\\)"} | grep gpaste")
+
+      with subtest("Open Cinnamon Settings"):
+          machine.succeed("${su "cinnamon-settings themes >&2 &"}")
+          machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'cinnamon-settings'")
+          machine.wait_for_text('(Style|Appearance|Color)')
+          machine.sleep(2)
+          machine.screenshot("cinnamon_settings")
+
+      with subtest("Check if screensaver works"):
+          # This is not supported at the moment.
+          # https://trello.com/b/HHs01Pab/cinnamon-wayland
+          machine.execute("${su "cinnamon-screensaver-command -l >&2 &"}")
+          machine.wait_until_succeeds("journalctl -b --grep 'cinnamon-screensaver is disabled in wayland sessions'")
+
+      with subtest("Open GNOME Terminal"):
+          machine.succeed("${su "dbus-launch gnome-terminal"}")
+          machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'gnome-terminal'")
+          machine.sleep(2)
+
+      with subtest("Check if Cinnamon has ever coredumped"):
+          machine.fail("coredumpctl --json=short | grep -E 'cinnamon|nemo'")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/cinnamon.nix b/nixpkgs/nixos/tests/cinnamon.nix
new file mode 100644
index 000000000000..eab907d0b712
--- /dev/null
+++ b/nixpkgs/nixos/tests/cinnamon.nix
@@ -0,0 +1,88 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "cinnamon";
+
+  meta.maintainers = lib.teams.cinnamon.members;
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.desktopManager.cinnamon.enable = true;
+
+    # For the sessionPath subtest.
+    services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gnome.gpaste ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0";
+      su = command: "su - ${user.name} -c '${env} ${command}'";
+
+      # Call javascript in cinnamon (the shell), returns a tuple (success, output),
+      # where `success` is true if the dbus call was successful and `output` is what
+      # the javascript evaluates to.
+      eval = name: su "gdbus call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval ${name}";
+    in
+    ''
+      machine.wait_for_unit("display-manager.service")
+
+      with subtest("Test if we can see username in slick-greeter"):
+          machine.wait_for_text("${user.description}")
+          machine.screenshot("slick_greeter_lightdm")
+
+      with subtest("Login with slick-greeter"):
+          machine.send_chars("${user.password}\n")
+          machine.wait_for_x()
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Wait for the Cinnamon shell"):
+          # Correct output should be (true, '2')
+          # https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
+          machine.wait_until_succeeds("${eval "Main.runState"} | grep -q 'true,..2'")
+
+      with subtest("Check if Cinnamon components actually start"):
+          for i in ["csd-media-keys", "cinnamon-killer-daemon", "xapp-sn-watcher", "nemo-desktop"]:
+            machine.wait_until_succeeds(f"pgrep -f {i}")
+          machine.wait_until_succeeds("journalctl -b --grep 'Loaded applet menu@cinnamon.org'")
+          machine.wait_until_succeeds("journalctl -b --grep 'calendar@cinnamon.org: Calendar events supported'")
+
+      with subtest("Check if sessionPath option actually works"):
+          machine.succeed("${eval "imports.gi.GIRepository.Repository.get_search_path\\(\\)"} | grep gpaste")
+
+      with subtest("Open Cinnamon Settings"):
+          machine.succeed("${su "cinnamon-settings themes >&2 &"}")
+          machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'cinnamon-settings'")
+          machine.wait_for_text('(Style|Appearance|Color)')
+          machine.sleep(2)
+          machine.screenshot("cinnamon_settings")
+
+      with subtest("Lock the screen"):
+          machine.succeed("${su "cinnamon-screensaver-command -l >&2 &"}")
+          machine.wait_until_succeeds("${su "cinnamon-screensaver-command -q"} | grep 'The screensaver is active'")
+          machine.sleep(2)
+          machine.screenshot("cinnamon_screensaver")
+          machine.send_chars("${user.password}\n", delay=0.2)
+          machine.wait_until_succeeds("${su "cinnamon-screensaver-command -q"} | grep 'The screensaver is inactive'")
+          machine.sleep(2)
+
+      with subtest("Open GNOME Terminal"):
+          machine.succeed("${su "gnome-terminal"}")
+          machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'gnome-terminal'")
+          machine.sleep(2)
+
+      with subtest("Open virtual keyboard"):
+          machine.succeed("${su "dbus-send --print-reply --dest=org.Cinnamon /org/Cinnamon org.Cinnamon.ToggleKeyboard"}")
+          machine.wait_for_text('(Ctrl|Alt)')
+          machine.sleep(2)
+          machine.screenshot("cinnamon_virtual_keyboard")
+
+      with subtest("Check if Cinnamon has ever coredumped"):
+          machine.fail("coredumpctl --json=short | grep -E 'cinnamon|nemo'")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/cjdns.nix b/nixpkgs/nixos/tests/cjdns.nix
new file mode 100644
index 000000000000..dc5f371c74d8
--- /dev/null
+++ b/nixpkgs/nixos/tests/cjdns.nix
@@ -0,0 +1,121 @@
+let
+  carolKey = "2d2a338b46f8e4a8c462f0c385b481292a05f678e19a2b82755258cf0f0af7e2";
+  carolPubKey = "n932l3pjvmhtxxcdrqq2qpw5zc58f01vvjx01h4dtd1bb0nnu2h0.k";
+  carolPassword = "678287829ce4c67bc8b227e56d94422ee1b85fa11618157b2f591de6c6322b52";
+
+  basicConfig =
+    { ... }:
+    { services.cjdns.enable = true;
+
+      # Turning off DHCP isn't very realistic but makes
+      # the sequence of address assignment less stochastic.
+      networking.useDHCP = false;
+
+      # CJDNS output is incompatible with the XML log.
+      systemd.services.cjdns.serviceConfig.StandardOutput = "null";
+    };
+
+in
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "cjdns";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ehmry ];
+  };
+
+  nodes = { # Alice finds peers over over ETHInterface.
+      alice =
+        { ... }:
+        { imports = [ basicConfig ];
+
+          services.cjdns.ETHInterface.bind = "eth1";
+
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          networking.firewall.allowedTCPPorts = [ 80 ];
+        };
+
+      # Bob explicitly connects to Carol over UDPInterface.
+      bob =
+        { ... }:
+
+        { imports = [ basicConfig ];
+
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = "192.168.0.2"; prefixLength = 24; }
+          ];
+
+          services.cjdns =
+            { UDPInterface =
+                { bind = "0.0.0.0:1024";
+                  connectTo."192.168.0.1:1024" =
+                    { password = carolPassword;
+                      publicKey = carolPubKey;
+                    };
+                };
+            };
+        };
+
+      # Carol listens on ETHInterface and UDPInterface,
+      # but knows neither Alice or Bob.
+      carol =
+        { ... }:
+        { imports = [ basicConfig ];
+
+          environment.etc."cjdns.keys".text = ''
+            CJDNS_PRIVATE_KEY=${carolKey}
+            CJDNS_ADMIN_PASSWORD=FOOBAR
+          '';
+
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = "192.168.0.1"; prefixLength = 24; }
+          ];
+
+          services.cjdns =
+            { authorizedPasswords = [ carolPassword ];
+              ETHInterface.bind = "eth1";
+              UDPInterface.bind = "192.168.0.1:1024";
+            };
+          networking.firewall.allowedUDPPorts = [ 1024 ];
+        };
+
+    };
+
+  testScript =
+    ''
+      import re
+
+      start_all()
+
+      alice.wait_for_unit("cjdns.service")
+      bob.wait_for_unit("cjdns.service")
+      carol.wait_for_unit("cjdns.service")
+
+
+      def cjdns_ip(machine):
+          res = machine.succeed("ip -o -6 addr show dev tun0")
+          ip = re.split("\s+|/", res)[3]
+          machine.log("has ip {}".format(ip))
+          return ip
+
+
+      alice_ip6 = cjdns_ip(alice)
+      bob_ip6 = cjdns_ip(bob)
+      carol_ip6 = cjdns_ip(carol)
+
+      # ping a few times each to let the routing table establish itself
+
+      alice.succeed("ping -c 4 {}".format(carol_ip6))
+      bob.succeed("ping -c 4 {}".format(carol_ip6))
+
+      carol.succeed("ping -c 4 {}".format(alice_ip6))
+      carol.succeed("ping -c 4 {}".format(bob_ip6))
+
+      alice.succeed("ping -c 4 {}".format(bob_ip6))
+      bob.succeed("ping -c 4 {}".format(alice_ip6))
+
+      alice.wait_for_unit("httpd.service")
+
+      bob.succeed("curl --fail -g http://[{}]".format(alice_ip6))
+    '';
+})
diff --git a/nixpkgs/nixos/tests/clickhouse.nix b/nixpkgs/nixos/tests/clickhouse.nix
new file mode 100644
index 000000000000..77d6a7ab8be4
--- /dev/null
+++ b/nixpkgs/nixos/tests/clickhouse.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "clickhouse";
+  meta.maintainers = with pkgs.lib.maintainers; [ ];
+
+  nodes.machine = {
+    services.clickhouse.enable = true;
+    virtualisation.memorySize = 4096;
+  };
+
+  testScript =
+    let
+      # work around quote/substitution complexity by Nix, Perl, bash and SQL.
+      tableDDL = pkgs.writeText "ddl.sql" "CREATE TABLE `demo` (`value` FixedString(10)) engine = MergeTree PARTITION BY value ORDER BY tuple();";
+      insertQuery = pkgs.writeText "insert.sql" "INSERT INTO `demo` (`value`) VALUES ('foo');";
+      selectQuery = pkgs.writeText "select.sql" "SELECT * from `demo`";
+    in
+      ''
+        machine.start()
+        machine.wait_for_unit("clickhouse.service")
+        machine.wait_for_open_port(9000)
+
+        machine.succeed(
+            "cat ${tableDDL} | clickhouse-client"
+        )
+        machine.succeed(
+            "cat ${insertQuery} | clickhouse-client"
+        )
+        machine.succeed(
+            "cat ${selectQuery} | clickhouse-client | grep foo"
+        )
+      '';
+})
diff --git a/nixpkgs/nixos/tests/cloud-init-hostname.nix b/nixpkgs/nixos/tests/cloud-init-hostname.nix
new file mode 100644
index 000000000000..7c657cc9f6f9
--- /dev/null
+++ b/nixpkgs/nixos/tests/cloud-init-hostname.nix
@@ -0,0 +1,46 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  # Hostname can also be set through "hostname" in user-data.
+  # This is how proxmox configures hostname through cloud-init.
+  metadataDrive = pkgs.stdenv.mkDerivation {
+    name = "metadata";
+    buildCommand = ''
+      mkdir -p $out/iso
+
+      cat << EOF > $out/iso/user-data
+      #cloud-config
+      hostname: testhostname
+      EOF
+
+      cat << EOF > $out/iso/meta-data
+      instance-id: iid-local02
+      EOF
+
+      ${pkgs.cdrkit}/bin/genisoimage -volid cidata -joliet -rock -o $out/metadata.iso $out/iso
+    '';
+  };
+
+in makeTest {
+  name = "cloud-init-hostname";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lewo illustris ];
+  };
+
+  nodes.machine2 = { ... }: {
+    virtualisation.qemu.options = [ "-cdrom" "${metadataDrive}/metadata.iso" ];
+    services.cloud-init.enable = true;
+    networking.hostName = "";
+  };
+
+  testScript = ''
+    unnamed.wait_for_unit("cloud-final.service")
+    assert "testhostname" in unnamed.succeed("hostname")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/cloud-init.nix b/nixpkgs/nixos/tests/cloud-init.nix
new file mode 100644
index 000000000000..0b4c5a55c80a
--- /dev/null
+++ b/nixpkgs/nixos/tests/cloud-init.nix
@@ -0,0 +1,115 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  inherit (import ./ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+
+  metadataDrive = pkgs.stdenv.mkDerivation {
+    name = "metadata";
+    buildCommand = ''
+      mkdir -p $out/iso
+
+      cat << EOF > $out/iso/user-data
+      #cloud-config
+      write_files:
+      -   content: |
+                cloudinit
+          path: /tmp/cloudinit-write-file
+
+      users:
+        - default
+        - name: nixos
+          ssh_authorized_keys:
+            - "${snakeOilPublicKey}"
+      EOF
+
+      cat << EOF > $out/iso/meta-data
+      instance-id: iid-local01
+      local-hostname: "test"
+      public-keys:
+        - "${snakeOilPublicKey}"
+      EOF
+
+      cat << EOF > $out/iso/network-config
+      version: 1
+      config:
+          - type: physical
+            name: eth0
+            mac_address: '52:54:00:12:34:56'
+            subnets:
+            - type: static
+              address: '12.34.56.78'
+              netmask: '255.255.255.0'
+              gateway: '12.34.56.9'
+          - type: nameserver
+            address:
+            - '6.7.8.9'
+            search:
+            - 'example.com'
+      EOF
+      ${pkgs.cdrkit}/bin/genisoimage -volid cidata -joliet -rock -o $out/metadata.iso $out/iso
+      '';
+  };
+
+in makeTest {
+  name = "cloud-init";
+  meta.maintainers = with pkgs.lib.maintainers; [ lewo illustris ];
+  nodes.machine = { ... }:
+  {
+    virtualisation.qemu.options = [ "-cdrom" "${metadataDrive}/metadata.iso" ];
+    services.cloud-init = {
+      enable = true;
+      network.enable = true;
+    };
+    services.openssh.enable = true;
+    networking.hostName = "";
+    networking.useDHCP = false;
+  };
+  testScript = ''
+    # To wait until cloud-init terminates its run
+    unnamed.wait_for_unit("cloud-init-local.service")
+    unnamed.wait_for_unit("cloud-final.service")
+
+    unnamed.succeed("cat /tmp/cloudinit-write-file | grep -q 'cloudinit'")
+
+    # install snakeoil ssh key and provision .ssh/config file
+    unnamed.succeed("mkdir -p ~/.ssh")
+    unnamed.succeed(
+        "cat ${snakeOilPrivateKey} > ~/.ssh/id_snakeoil"
+    )
+    unnamed.succeed("chmod 600 ~/.ssh/id_snakeoil")
+
+    unnamed.wait_for_unit("sshd.service")
+
+    # we should be able to log in as the root user, as well as the created nixos user
+    unnamed.succeed(
+        "timeout 10 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil root@localhost 'true'"
+    )
+    unnamed.succeed(
+        "timeout 10 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil nixos@localhost 'true'"
+    )
+
+    # test changing hostname via cloud-init worked
+    assert (
+        unnamed.succeed(
+            "timeout 10 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/id_snakeoil nixos@localhost 'hostname'"
+        ).strip()
+        == "test"
+    )
+
+    # check IP and route configs
+    assert "default via 12.34.56.9 dev eth0 proto static" in unnamed.succeed("ip route")
+    assert "12.34.56.0/24 dev eth0 proto kernel scope link src 12.34.56.78" in unnamed.succeed("ip route")
+
+    # check nameserver and search configs
+    assert "6.7.8.9" in unnamed.succeed("resolvectl status")
+    assert "example.com" in unnamed.succeed("resolvectl status")
+
+  '';
+}
diff --git a/nixpkgs/nixos/tests/cloudlog.nix b/nixpkgs/nixos/tests/cloudlog.nix
new file mode 100644
index 000000000000..c99951c1b22c
--- /dev/null
+++ b/nixpkgs/nixos/tests/cloudlog.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "cloudlog";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ melling ];
+  };
+  nodes = {
+    machine = {
+      services.mysql.package = pkgs.mariadb;
+      services.cloudlog.enable = true;
+    };
+  };
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("phpfpm-cloudlog")
+    machine.wait_for_open_port(80);
+    machine.wait_until_succeeds("curl -s -L --fail http://localhost | grep 'Login - Cloudlog'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/cntr.nix b/nixpkgs/nixos/tests/cntr.nix
new file mode 100644
index 000000000000..598143beb6c0
--- /dev/null
+++ b/nixpkgs/nixos/tests/cntr.nix
@@ -0,0 +1,75 @@
+# Test for cntr tool
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; }, lib ? pkgs.lib }:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+
+  mkOCITest = backend:
+    makeTest {
+      name = "cntr-${backend}";
+
+      meta = { maintainers = with lib.maintainers; [ sorki mic92 ]; };
+
+      nodes = {
+        ${backend} = { pkgs, ... }: {
+          environment.systemPackages = [ pkgs.cntr ];
+          virtualisation.oci-containers = {
+            inherit backend;
+            containers.nginx = {
+              image = "nginx-container";
+              imageFile = pkgs.dockerTools.examples.nginx;
+              ports = [ "8181:80" ];
+            };
+          };
+        };
+      };
+
+      testScript = ''
+        start_all()
+        ${backend}.wait_for_unit("${backend}-nginx.service")
+        ${backend}.wait_for_open_port(8181)
+        # For some reason, the cntr command hangs when run without the &.
+        # As such, we have to do some messy things to ensure we check the exitcode and output in a race-condition-safe manner
+        ${backend}.execute(
+            "(cntr attach -t ${backend} nginx sh -- -c 'curl localhost | grep Hello' > /tmp/result; echo $? > /tmp/exitcode; touch /tmp/done) &"
+        )
+
+        ${backend}.wait_for_file("/tmp/done")
+        assert "0" == ${backend}.succeed("cat /tmp/exitcode").strip(), "non-zero exit code"
+        assert "Hello" in ${backend}.succeed("cat /tmp/result"), "no greeting in output"
+      '';
+    };
+
+  mkContainersTest = makeTest {
+    name = "cntr-containers";
+
+    meta = with pkgs.lib.maintainers; { maintainers = [ sorki mic92 ]; };
+
+    nodes.machine = { lib, ... }: {
+      environment.systemPackages = [ pkgs.cntr ];
+      containers.test = {
+        autoStart = true;
+        privateNetwork = true;
+        hostAddress = "172.16.0.1";
+        localAddress = "172.16.0.2";
+        config = { };
+      };
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("container@test.service")
+      # I haven't observed the same hanging behaviour in this version as in the OCI version which necessetates this messy invocation, but it's probably better to be safe than sorry and use it here as well
+      machine.execute(
+          "(cntr attach test sh -- -c 'ping -c5 172.16.0.1'; echo $? > /tmp/exitcode; touch /tmp/done) &"
+      )
+
+      machine.wait_for_file("/tmp/done")
+      assert "0" == machine.succeed("cat /tmp/exitcode").strip(), "non-zero exit code"
+    '';
+  };
+in {
+  nixos-container = mkContainersTest;
+} // (lib.foldl' (attrs: backend: attrs // { ${backend} = mkOCITest backend; })
+  { } [ "docker" "podman" ])
diff --git a/nixpkgs/nixos/tests/cockpit.nix b/nixpkgs/nixos/tests/cockpit.nix
new file mode 100644
index 000000000000..e7165b979014
--- /dev/null
+++ b/nixpkgs/nixos/tests/cockpit.nix
@@ -0,0 +1,136 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+
+  let
+    user = "alice"; # from ./common/user-account.nix
+    password = "foobar"; # from ./common/user-account.nix
+  in {
+    name = "cockpit";
+    meta = {
+      maintainers = with lib.maintainers; [ lucasew ];
+    };
+    nodes = {
+      server = { config, ... }: {
+        imports = [ ./common/user-account.nix ];
+        security.polkit.enable = true;
+        users.users.${user} = {
+          extraGroups = [ "wheel" ];
+        };
+        services.cockpit = {
+          enable = true;
+          openFirewall = true;
+          settings = {
+            WebService = {
+              Origins = "https://server:9090";
+            };
+          };
+        };
+      };
+      client = { config, ... }: {
+        imports = [ ./common/user-account.nix ];
+        environment.systemPackages = let
+          seleniumScript = pkgs.writers.writePython3Bin "selenium-script" {
+            libraries = with pkgs.python3Packages; [ selenium ];
+            } ''
+            from selenium import webdriver
+            from selenium.webdriver.common.by import By
+            from selenium.webdriver.firefox.options import Options
+            from selenium.webdriver.support.ui import WebDriverWait
+            from selenium.webdriver.support import expected_conditions as EC
+            from time import sleep
+
+
+            def log(msg):
+                from sys import stderr
+                print(f"[*] {msg}", file=stderr)
+
+
+            log("Initializing")
+
+            options = Options()
+            options.add_argument("--headless")
+
+            service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+            driver = webdriver.Firefox(options=options, service=service)
+
+            driver.implicitly_wait(10)
+
+            log("Opening homepage")
+            driver.get("https://server:9090")
+
+            wait = WebDriverWait(driver, 60)
+
+
+            def wait_elem(by, query):
+                wait.until(EC.presence_of_element_located((by, query)))
+
+
+            def wait_title_contains(title):
+                wait.until(EC.title_contains(title))
+
+
+            def find_element(by, query):
+                return driver.find_element(by, query)
+
+
+            def set_value(elem, value):
+                script = 'arguments[0].value = arguments[1]'
+                return driver.execute_script(script, elem, value)
+
+
+            log("Waiting for the homepage to load")
+
+            # cockpit sets initial title as hostname
+            wait_title_contains("server")
+            wait_elem(By.CSS_SELECTOR, 'input#login-user-input')
+
+            log("Homepage loaded!")
+
+            log("Filling out username")
+            login_input = find_element(By.CSS_SELECTOR, 'input#login-user-input')
+            set_value(login_input, "${user}")
+
+            log("Filling out password")
+            password_input = find_element(By.CSS_SELECTOR, 'input#login-password-input')
+            set_value(password_input, "${password}")
+
+            log("Submitting credentials for login")
+            driver.find_element(By.CSS_SELECTOR, 'button#login-button').click()
+
+            # driver.implicitly_wait(1)
+            # driver.get("https://server:9090/system")
+
+            log("Waiting dashboard to load")
+            wait_title_contains("${user}@server")
+
+            log("Waiting for the frontend to initialize")
+            sleep(1)
+
+            log("Looking for that banner that tells about limited access")
+            container_iframe = find_element(By.CSS_SELECTOR, 'iframe.container-frame')
+            driver.switch_to.frame(container_iframe)
+
+            assert "Web console is running in limited access mode" in driver.page_source
+
+            driver.close()
+          '';
+        in with pkgs; [ firefox-unwrapped geckodriver seleniumScript ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      server.wait_for_open_port(9090)
+      server.wait_for_unit("network.target")
+      server.wait_for_unit("multi-user.target")
+      server.systemctl("start", "polkit")
+
+      client.wait_for_unit("multi-user.target")
+
+      client.succeed("curl -k https://server:9090 -o /dev/stderr")
+      print(client.succeed("whoami"))
+      client.succeed('PYTHONUNBUFFERED=1 selenium-script')
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/cockroachdb.nix b/nixpkgs/nixos/tests/cockroachdb.nix
new file mode 100644
index 000000000000..5b1e1a7dee1f
--- /dev/null
+++ b/nixpkgs/nixos/tests/cockroachdb.nix
@@ -0,0 +1,124 @@
+# This performs a full 'end-to-end' test of a multi-node CockroachDB cluster
+# using the built-in 'cockroach workload' command, to simulate a semi-realistic
+# test load. It generally takes anywhere from 3-5 minutes to run and 1-2GB of
+# RAM (though each of 3 workers gets 2GB allocated)
+#
+# CockroachDB requires synchronized system clocks within a small error window
+# (~500ms by default) on each node in order to maintain a multi-node cluster.
+# Cluster joins that are outside this window will fail, and nodes that skew
+# outside the window after joining will promptly get kicked out.
+#
+# To accommodate this, we use QEMU/virtio infrastructure and load the 'ptp_kvm'
+# driver inside a guest. This driver allows the host machine to pass its clock
+# through to the guest as a hardware clock that appears as a Precision Time
+# Protocol (PTP) Clock device, generally /dev/ptp0. PTP devices can be measured
+# and used as hardware reference clocks (similar to an on-board GPS clock) by
+# NTP software. In our case, we use Chrony to synchronize to the reference
+# clock.
+#
+# This test is currently NOT enabled as a continuously-checked NixOS test.
+# Ideally, this test would be run by Hydra and Borg on all relevant changes,
+# except:
+#
+#   - Not every build machine is compatible with the ptp_kvm driver.
+#     Virtualized EC2 instances, for example, do not support loading the ptp_kvm
+#     driver into guests. However, bare metal builders (e.g. Packet) do seem to
+#     work just fine. In practice, this means x86_64-linux builds would fail
+#     randomly, depending on which build machine got the job. (This is probably
+#     worth some investigation; I imagine it's based on ptp_kvm's usage of paravirt
+#     support which may not be available in 'nested' environments.)
+#
+#   - ptp_kvm is not supported on aarch64, otherwise it seems likely Cockroach
+#     could be tested there, as well. This seems to be due to the usage of
+#     the TSC in ptp_kvm, which isn't supported (easily) on AArch64. (And:
+#     testing stuff, not just making sure it builds, is important to ensure
+#     aarch64 support remains viable.)
+#
+# For future developers who are reading this message, are daring and would want
+# to fix this, some options are:
+#
+#   - Just test a single node cluster instead (boring and less thorough).
+#   - Move all CI to bare metal packet builders, and we can at least do x86_64-linux.
+#   - Get virtualized clocking working in aarch64, somehow.
+#   - Add a 4th node that acts as an NTP service and uses no PTP clocks for
+#     references, at the client level. This bloats the node and memory
+#     requirements, but would probably allow both aarch64/x86_64 to work.
+#
+
+let
+
+  # Creates a node. If 'joinNode' parameter, a string containing an IP address,
+  # is non-null, then the CockroachDB server will attempt to join/connect to
+  # the cluster node specified at that address.
+  makeNode = locality: myAddr: joinNode:
+    { nodes, pkgs, lib, config, ... }:
+
+    {
+      # Bank/TPC-C benchmarks take some memory to complete
+      virtualisation.memorySize = 2048;
+
+      # Install the KVM PTP "Virtualized Clock" driver. This allows a /dev/ptp0
+      # device to appear as a reference clock, synchronized to the host clock.
+      # Because CockroachDB *requires* a time-synchronization mechanism for
+      # the system time in a cluster scenario, this is necessary to work.
+      boot.kernelModules = [ "ptp_kvm" ];
+
+      # Enable and configure Chrony, using the given virtualized clock passed
+      # through by KVM.
+      services.chrony.enable = true;
+      services.chrony.servers = lib.mkForce [ ];
+      services.chrony.extraConfig = ''
+        refclock PHC /dev/ptp0 poll 2 prefer require refid KVM
+        makestep 0.1 3
+      '';
+
+      # Enable CockroachDB. In order to ensure that Chrony has performed its
+      # first synchronization at boot-time (which may take ~10 seconds) before
+      # starting CockroachDB, we block the ExecStartPre directive using the
+      # 'waitsync' command. This ensures Cockroach doesn't have its system time
+      # leap forward out of nowhere during startup/execution.
+      #
+      # Note that the default threshold for NTP-based skew in CockroachDB is
+      # ~500ms by default, so making sure it's started *after* accurate time
+      # synchronization is extremely important.
+      services.cockroachdb.enable = true;
+      services.cockroachdb.insecure = true;
+      services.cockroachdb.openPorts = true;
+      services.cockroachdb.locality = locality;
+      services.cockroachdb.listen.address = myAddr;
+      services.cockroachdb.join = lib.mkIf (joinNode != null) joinNode;
+
+      systemd.services.chronyd.unitConfig.ConditionPathExists = "/dev/ptp0";
+
+      # Hold startup until Chrony has performed its first measurement (which
+      # will probably result in a full timeskip, thanks to makestep)
+      systemd.services.cockroachdb.preStart = ''
+        ${pkgs.chrony}/bin/chronyc waitsync
+      '';
+    };
+
+in import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "cockroachdb";
+  meta.maintainers = with pkgs.lib.maintainers;
+    [ thoughtpolice ];
+
+  nodes = {
+    node1 = makeNode "country=us,region=east,dc=1"  "192.168.1.1" null;
+    node2 = makeNode "country=us,region=west,dc=2b" "192.168.1.2" "192.168.1.1";
+    node3 = makeNode "country=eu,region=west,dc=2"  "192.168.1.3" "192.168.1.1";
+  };
+
+  # NOTE: All the nodes must start in order and you must NOT use startAll, because
+  # there's otherwise no way to guarantee that node1 will start before the others try
+  # to join it.
+  testScript = ''
+    for node in node1, node2, node3:
+        node.start()
+        node.wait_for_unit("cockroachdb")
+    node1.succeed(
+        "cockroach sql --host=192.168.1.1 --insecure -e 'SHOW ALL CLUSTER SETTINGS' 2>&1",
+        "cockroach workload init bank 'postgresql://root@192.168.1.1:26257?sslmode=disable'",
+        "cockroach workload run bank --duration=1m 'postgresql://root@192.168.1.1:26257?sslmode=disable'",
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/code-server.nix b/nixpkgs/nixos/tests/code-server.nix
new file mode 100644
index 000000000000..7d523dfc617e
--- /dev/null
+++ b/nixpkgs/nixos/tests/code-server.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+{
+  name = "code-server";
+
+  nodes = {
+    machine = {pkgs, ...}: {
+      services.code-server = {
+        enable = true;
+        auth = "none";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("code-server.service")
+    machine.wait_for_open_port(4444)
+    machine.succeed("curl -k --fail http://localhost:4444", timeout=10)
+  '';
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+})
diff --git a/nixpkgs/nixos/tests/coder.nix b/nixpkgs/nixos/tests/coder.nix
new file mode 100644
index 000000000000..12813827284b
--- /dev/null
+++ b/nixpkgs/nixos/tests/coder.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "coder";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ shyim ghuntley ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.coder = {
+        enable = true;
+        accessUrl = "http://localhost:3000";
+      };
+    };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("postgresql.service")
+    machine.wait_for_unit("coder.service")
+    machine.wait_for_open_port(3000)
+
+    machine.succeed("curl --fail http://localhost:3000")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/collectd.nix b/nixpkgs/nixos/tests/collectd.nix
new file mode 100644
index 000000000000..2480bdb5f917
--- /dev/null
+++ b/nixpkgs/nixos/tests/collectd.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "collectd";
+  meta = { };
+
+  nodes.machine =
+    { pkgs, lib, ... }:
+
+    {
+      services.collectd = {
+        enable = true;
+        extraConfig = lib.mkBefore ''
+          Interval 30
+        '';
+        plugins = {
+          rrdtool = ''
+            DataDir "/var/lib/collectd/rrd"
+          '';
+          load = "";
+        };
+      };
+      environment.systemPackages = [ pkgs.rrdtool ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit("collectd.service")
+    hostname = machine.succeed("hostname").strip()
+    file = f"/var/lib/collectd/rrd/{hostname}/load/load.rrd"
+    machine.wait_for_file(file);
+    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/nixpkgs/nixos/tests/common/acme/client/default.nix b/nixpkgs/nixos/tests/common/acme/client/default.nix
new file mode 100644
index 000000000000..503e610d1ac9
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/client/default.nix
@@ -0,0 +1,16 @@
+{ lib, nodes, pkgs, ... }:
+let
+  caCert = nodes.acme.test-support.acme.caCert;
+  caDomain = nodes.acme.test-support.acme.caDomain;
+
+in {
+  security.acme = {
+    acceptTerms = true;
+    defaults = {
+      server = "https://${caDomain}/dir";
+      email = "hostmaster@example.test";
+    };
+  };
+
+  security.pki.certificateFiles = [ caCert ];
+}
diff --git a/nixpkgs/nixos/tests/common/acme/server/README.md b/nixpkgs/nixos/tests/common/acme/server/README.md
new file mode 100644
index 000000000000..9de2b2c71029
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/server/README.md
@@ -0,0 +1,21 @@
+# Fake Certificate Authority for ACME testing
+
+This will set up a test node running [pebble](https://github.com/letsencrypt/pebble)
+to serve ACME certificate requests.
+
+## "Snake oil" certs
+
+The snake oil certs are hard coded into the repo for reasons explained [here](https://github.com/NixOS/nixpkgs/pull/91121#discussion_r505410235).
+The root of the issue is that Nix will hash the derivation based on the arguments
+to mkDerivation, not the output. [Minica](https://github.com/jsha/minica) will
+always generate a random certificate even if the arguments are unchanged. As a
+result, it's possible to end up in a situation where the cached and local
+generated certs mismatch and cause issues with testing.
+
+To generate new certificates, run the following commands:
+
+```bash
+nix-build generate-certs.nix
+cp result/* .
+rm result
+```
diff --git a/nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem b/nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem
new file mode 100644
index 000000000000..48f488ab8f90
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/server/acme.test.cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDLDCCAhSgAwIBAgIIajCXIUnozqQwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVbWluaWNhIHJvb3QgY2EgMjMwYjU4MB4XDTIyMTEyMTE3MTcxMFoXDTQyMTEy
+MTE3MTcxMFowFDESMBAGA1UEAxMJYWNtZS50ZXN0MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA5INxJwKDVYNfTnkXwvKM/SufBNjvxWZxlkaMFbkAN5wJ
+6HwuesRZE9IgfRO9N+rSq1U2lDBm9gFPERqsQJVZHHJ5kkaNUr89h25+wgX5emGy
+UV2KEpCFssDD4aSBF+b0sryguCa1ZRj9b+pdfRxiYaORjSh5UzlXZoRm9iwHdzHT
+oKLlmqozqzEt0o9qpZL8gv+rv8C5BGOY6hfXAHYmkWRt87FN5BkSjgEWiY++DOAU
+X0TdobdSTrs/xJP+IbadRchqTH2kiG0g2BoCSXUsl7Mdh4IOUeQGDz/F5tH8PAtz
+p3dyjdQEFex2J5tlScLfVHoCBKV3gpCg+Keuum2j8QIDAQABo3YwdDAOBgNVHQ8B
+Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB
+/wQCMAAwHwYDVR0jBBgwFoAUvTCE3Lj/P6OWkmOGtsjcTcIDzkAwFAYDVR0RBA0w
+C4IJYWNtZS50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQAvZM4Ik1NOXQfbPRgbolyL
+b3afsSHbhHl9B2f0HGi5EAPdwyeWZsK3BF+SKFGAW5BlXr2SSlW/MQOMiUKTadnS
+8xTOFc1Ws8JWWc82zQqWcOWEXhU+AI8p70sTVFeXPWwLFy3nBRwDH4ZPU8UFHeje
+YXqbfxrsdEFXrbCfWSzPQP24xqVt7n9Am/5XFGtDkRsYlVgLwq/F6lN9hO0/gYIx
+8NsZ8Xy+QvBlGL+z9Zo7EylB8bP9OBtOtEv9fZcnxgughieiTDs36GwdQRE2aI+d
+cG3lQX8NGxgcpDoH8+rNx7Uw7odg0gVbI3agyyvax6DPht+/bzXmHm8ogklGTOoG
+-----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem b/nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem
new file mode 100644
index 000000000000..4837f19b3024
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/server/acme.test.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA5INxJwKDVYNfTnkXwvKM/SufBNjvxWZxlkaMFbkAN5wJ6Hwu
+esRZE9IgfRO9N+rSq1U2lDBm9gFPERqsQJVZHHJ5kkaNUr89h25+wgX5emGyUV2K
+EpCFssDD4aSBF+b0sryguCa1ZRj9b+pdfRxiYaORjSh5UzlXZoRm9iwHdzHToKLl
+mqozqzEt0o9qpZL8gv+rv8C5BGOY6hfXAHYmkWRt87FN5BkSjgEWiY++DOAUX0Td
+obdSTrs/xJP+IbadRchqTH2kiG0g2BoCSXUsl7Mdh4IOUeQGDz/F5tH8PAtzp3dy
+jdQEFex2J5tlScLfVHoCBKV3gpCg+Keuum2j8QIDAQABAoIBAHfnUHQ7qVYxfMzc
+VU+BneEqBmKwwf8+ZdOIaPDtBeQoCDrpDip05Ji15T48IUk5+hjUubLAQwZKYYaE
+DGZG918p4giS5IzKtCpgHDsKj4FbyglPn6dmFgFZjG7VtrcoBLXUrDB0fzHxDuqu
+eyeuwSCihzkeR6sXp3iveKcrKy+rA31aqWvJZb24qyAu1y8KIcf2ZMUiYcJF2kpL
+XZz4uyx4x/B9NE+PmLqo7x/9iS+p5aT2kWVCVUGmhII0ChFnWSnjxqecBMhWFY1O
+3U0lKhloj6UKBya91hGospEJdaLHpHCWUgYPvA5mG+48kqYkPkecmTf8Xha3TxPf
+g1qv3sECgYEA+hMO1qTlnqhBajCMcAGIlpRHwr97hQMdSylHBXob1xCnuTEJKHOo
+7UmQw9hJgD4JgYxcivg/OFErXdefbSae9NqSNdOshxmrxz6DFTN3Ms3WR1I1be3c
+B2mpGllMPbxJ3CKFet2CQSvOM9jfbK68R7Jlhiap0bESvWrT9ztUCWUCgYEA6e2Y
+iMNNo1dWushSMVvCkWR9CLAsnWnjFG4FYIPz/iuxJjRXDiWyR6x4WYjUx3ZBhpf5
+wVFUK7VaPJBfOn7KCan59dqOvL3LSB/5SupwRMecCEhYPQvSaxn4MNrx0Vi83O4C
+togyD9/UJ4ji+TXwMj2eMzwRspmO/26hXkQGzZ0CgYEA0qlLTrYKWOUUdgf/xjsE
+fRTcfsofm6VMAAz9rzd2TG3TXMZaGKGWJI5cTR7ejBG2oFNFgiwt1ZtLFPqXarOm
+JE4b7QwrwoN1mZqngiygtUOAxwQRzlEZkYUI1xFykG8VKURLfX0sRQpJ4pNHY56v
+LRazP5dCZ0rrpnVfql1oJaECgYEAxtvT728XcOOuNtpUBOGcZTynjds2EhsRjyx4
+JbQGlutNjMyxtLUW+RcEuBg5ydYdne1Tw6L/iqiALTwNuAxQdCaq9vT0oj41sPp9
+UdI53j5Rxji5yitilOlesylsqCpnYuhyJflhlV0RXQpg6LmRlyQKeEN4R/uCNGI3
+i4sIvYECgYEA4DC2qObfB0UkN81uGluwwM5rR04qvIc5xX3QIvHuIJOs/uP54daD
+OiEDTxTpiqDNsFL0Pyl07aL7jubHNqU/eQpQIEZRlDy4Mr31QSbQ9R2/NNBwHu22
+BnnNKzZ97T0NVgxJXOqcOlRGjwb/5OUDpaIClJY+GqilEdOeu7Pl3aA=
+-----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/common/acme/server/ca.cert.pem b/nixpkgs/nixos/tests/common/acme/server/ca.cert.pem
new file mode 100644
index 000000000000..b6f2b9e3a91f
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/server/ca.cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSzCCAjOgAwIBAgIIIwtYp+WlBbswDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVbWluaWNhIHJvb3QgY2EgMjMwYjU4MCAXDTIyMTEyMTE3MTcxMFoYDzIxMjIx
+MTIxMTcxNzEwWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAyMzBiNTgwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvqAoAyV8igrmBnU6T1nQDfkkQ
+HjQp+ANCthNCi4kGPOoTxrYrUMWa6d/aSIv5hKO2A+r2GdTeM1RvSo6GUr3GmsJc
+WUMbIsJ0SJSLQEyvmFPpzfV3NdfIt6vZRiqJbLt7yuDiZil33GdQEKYywJxIsCb2
+CSd55V1cZSiLItWEIURAhHhSxHabMRmIF/xZWxKFEDeagzXOxUBPAvIwzzqQroBv
+3vZhfgcAjCyS0crJ/E2Wa6GLKfFvaXGEj/KlXftwpbvFtnNBtmtJcNy9a8LJoOcA
+E+ZjD21hidnCc+Yag7LaR3ZtAVkpeRJ9rRNBkVP4rv2mq2skIkgDfY/F8smPAgMB
+AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
+BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS9MITcuP8/o5aS
+Y4a2yNxNwgPOQDAfBgNVHSMEGDAWgBS9MITcuP8/o5aSY4a2yNxNwgPOQDANBgkq
+hkiG9w0BAQsFAAOCAQEADCcgaxrI/pqjkYb0c3QHwfKCNz4khSWs/9tBpBfdxdUX
+uvG7rZzVW7pkzML+m4tSo2wm9sHRAgG+dIpzbSoRTouMntWlvYEnrr1SCw4NyBo1
+cwmNUz4JL+E3dnpI4FSOpyFyO87qL9ep0dxQEADWSppyCA762wfFpY+FvT6b/he8
+eDEc/Umjfm+X0tqNWx3aVoeyIJT46AeElry2IRLAk7z/vEPGFFzgd2Jh6Qsdeagk
+YkU0tFl9q9BotPYGlCMtVjmzbJtxh4uM9YCgiz1THzFjrUvfaTM8VjuBxbpoCZkS
+85mNhFZvNq8/cgYc0kYZOg8+jRdy87xmTRp64LBd6w==
+-----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/common/acme/server/ca.key.pem b/nixpkgs/nixos/tests/common/acme/server/ca.key.pem
new file mode 100644
index 000000000000..5d46c025788f
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/server/ca.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAr6gKAMlfIoK5gZ1Ok9Z0A35JEB40KfgDQrYTQouJBjzqE8a2
+K1DFmunf2kiL+YSjtgPq9hnU3jNUb0qOhlK9xprCXFlDGyLCdEiUi0BMr5hT6c31
+dzXXyLer2UYqiWy7e8rg4mYpd9xnUBCmMsCcSLAm9gkneeVdXGUoiyLVhCFEQIR4
+UsR2mzEZiBf8WVsShRA3moM1zsVATwLyMM86kK6Ab972YX4HAIwsktHKyfxNlmuh
+iynxb2lxhI/ypV37cKW7xbZzQbZrSXDcvWvCyaDnABPmYw9tYYnZwnPmGoOy2kd2
+bQFZKXkSfa0TQZFT+K79pqtrJCJIA32PxfLJjwIDAQABAoIBAErEFJXnIIY47Cq+
+QS7t7e16uDCTGpLujLy9cQ83AzjTfrKyNuHS/HkGqRBpJqMrEN+tZTohHpkBciP4
+sRd9amd5gdb663RGZExIhGmNEdb/2F/BGYUHNvSpMQ1HL13VGSwE25mh8G6jMppC
+q+sYTq0lxT+d/96DgSyNpicqyYT2S2CTCRkWGAsc6KQwRpBYqoEqUeakyGfe2k85
+pj32H53Si/49fkWkQ9RciPdg7qcu7u/iegwAkkjKoATeEjNf0NqBlkWag1qU0UHR
+r2xDin+3ffEU2GQEwSvnGwlo7uyAN0UsryEWa9suuhX5T4eSWAMgTL4iVkh8Aa24
++YEFOGkCgYEA0DUb++31+nuxU8N+GPaPQXiob8C0RmSzSzSHJ3daJpzq8k576jqs
+3TgkhLDzQepcTYVU2ucn6+9ziXEsz4H06W3FNGktnyK4BRqYitt5TjZvPc+WTPhR
+0U+iUqBZilCAhUkIsNUiGvnMhz9VfcS/gn+NqhL7kvYi11/jAc4bbB0CgYEA1/oh
++t1ZKVLkbANrma/M8AX27Vl3k4jgOWGzFwAVD10zN31gGyVjv1knmG22pmL2+N+Z
+8CnVmdHQQQIWV1pYbgwRkvpnZWyH7AvHd9l1XLYyOU3VEpz+e2bpMtzesaza3UWW
+k8NELNE5sBopY939XkQ9G3aMXtbkx01zX+0BZJsCgYB+MdJ2TfKrEVGXfYPuSXLm
+seUVZu1dRSfOy1WnvBVuFenpV1yPyWSA6MhpjH7EUvIDIm8eBsERpZ6XjXslgpUY
+7ql6bM10CK0UmtwePYw2tZOTGUD2AgRFI0k1X28mAEkFgBC+bVAwnXsz9lUw15Fj
+3T/V9493savIcpu6uluwmQKBgQCE/I4jzFv0aAgiwlBlB6znNqT/LRHGFIgMjS4b
+QX+2QCsjRd4BmRo8XodVAmlvNozgXb6J9RiDaIAVJ1XeX9EHogLIP8ue1h8zp2Uh
+VRNBDScLxfMnTOgd0BZTrVCqkscJbKn1Pk0iU4pz9wf5aF10yAvgdzSjySqB1hzu
+uh8bdQKBgEpFIyhqfXf/NzchI5y23Cok14LFIPJ1yERD/B8taS7muVQwpgffy+Ld
+BH7dhafWSDVqIk1e6yl+82b4amleTEmDfopgc6FR7uPid1JoFxrwhnEfC3FjZamp
+1OzXAOE/mX3jHf1spqpB2J/rDVPKi934ocQVoWnBeRopGTXxzbed
+-----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/common/acme/server/default.nix b/nixpkgs/nixos/tests/common/acme/server/default.nix
new file mode 100644
index 000000000000..2a2e3b08a1df
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/server/default.nix
@@ -0,0 +1,141 @@
+# The certificate for the ACME service is exported as:
+#
+#   config.test-support.acme.caCert
+#
+# This value can be used inside the configuration of other test nodes to inject
+# the test certificate into security.pki.certificateFiles or into package
+# overlays.
+#
+# Another value that's needed if you don't use a custom resolver (see below for
+# notes on that) is to add the acme node as a nameserver to every node
+# that needs to acquire certificates using ACME, because otherwise the API host
+# for acme.test can't be resolved.
+#
+# A configuration example of a full node setup using this would be this:
+#
+# {
+#   acme = import ./common/acme/server;
+#
+#   example = { nodes, ... }: {
+#     networking.nameservers = [
+#       nodes.acme.networking.primaryIPAddress
+#     ];
+#     security.pki.certificateFiles = [
+#       nodes.acme.test-support.acme.caCert
+#     ];
+#   };
+# }
+#
+# By default, this module runs a local resolver, generated using resolver.nix
+# from the parent directory to automatically discover all zones in the network.
+#
+# If you do not want this and want to use your own resolver, you can just
+# override networking.nameservers like this:
+#
+# {
+#   acme = { nodes, lib, ... }: {
+#     imports = [ ./common/acme/server ];
+#     networking.nameservers = lib.mkForce [
+#       nodes.myresolver.networking.primaryIPAddress
+#     ];
+#   };
+#
+#   myresolver = ...;
+# }
+#
+# Keep in mind, that currently only _one_ resolver is supported, if you have
+# more than one resolver in networking.nameservers only the first one will be
+# used.
+#
+# Also make sure that whenever you use a resolver from a different test node
+# that it has to be started _before_ the ACME service.
+{ config, pkgs, lib, ... }:
+let
+  testCerts = import ./snakeoil-certs.nix;
+  domain = testCerts.domain;
+
+  resolver = let
+    message = "You need to define a resolver for the acme test module.";
+    firstNS = lib.head config.networking.nameservers;
+  in if config.networking.nameservers == [] then throw message else firstNS;
+
+  pebbleConf.pebble = {
+    listenAddress = "0.0.0.0:443";
+    managementListenAddress = "0.0.0.0:15000";
+    # These certs and keys are used for the Web Front End (WFE)
+    certificate = testCerts.${domain}.cert;
+    privateKey = testCerts.${domain}.key;
+    httpPort = 80;
+    tlsPort = 443;
+    ocspResponderURL = "http://${domain}:4002";
+    strict = true;
+  };
+
+  pebbleConfFile = pkgs.writeText "pebble.conf" (builtins.toJSON pebbleConf);
+
+in {
+  imports = [ ../../resolver.nix ];
+
+  options.test-support.acme = {
+    caDomain = lib.mkOption {
+      type = lib.types.str;
+      readOnly = true;
+      default = domain;
+      description = lib.mdDoc ''
+        A domain name to use with the `nodes` attribute to
+        identify the CA server.
+      '';
+    };
+    caCert = lib.mkOption {
+      type = lib.types.path;
+      readOnly = true;
+      default = testCerts.ca.cert;
+      description = lib.mdDoc ''
+        A certificate file to use with the `nodes` attribute to
+        inject the test CA certificate used in the ACME server into
+        {option}`security.pki.certificateFiles`.
+      '';
+    };
+  };
+
+  config = {
+    test-support = {
+      resolver.enable = let
+        isLocalResolver = config.networking.nameservers == [ "127.0.0.1" ];
+      in lib.mkOverride 900 isLocalResolver;
+    };
+
+    # This has priority 140, because modules/testing/test-instrumentation.nix
+    # already overrides this with priority 150.
+    networking.nameservers = lib.mkOverride 140 [ "127.0.0.1" ];
+    networking.firewall.allowedTCPPorts = [ 80 443 15000 4002 ];
+
+    networking.extraHosts = ''
+      127.0.0.1 ${domain}
+      ${config.networking.primaryIPAddress} ${domain}
+    '';
+
+    systemd.services = {
+      pebble = {
+        enable = true;
+        description = "Pebble ACME server";
+        wantedBy = [ "network.target" ];
+        environment = {
+          # We're not testing lego, we're just testing our configuration.
+          # No need to sleep.
+          PEBBLE_VA_NOSLEEP = "1";
+        };
+
+        serviceConfig = {
+          RuntimeDirectory = "pebble";
+          WorkingDirectory = "/run/pebble";
+
+          # Required to bind on privileged ports.
+          AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+
+          ExecStart = "${pkgs.pebble}/bin/pebble -config ${pebbleConfFile}";
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/common/acme/server/generate-certs.nix b/nixpkgs/nixos/tests/common/acme/server/generate-certs.nix
new file mode 100644
index 000000000000..4f38ca309b05
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/server/generate-certs.nix
@@ -0,0 +1,33 @@
+# Minica can provide a CA key and cert, plus a key
+# and cert for our fake CA server's Web Front End (WFE).
+{
+  pkgs ? import <nixpkgs> {},
+  minica ? pkgs.minica,
+  mkDerivation ? pkgs.stdenv.mkDerivation
+}:
+let
+  conf = import ./snakeoil-certs.nix;
+  domain = conf.domain;
+in mkDerivation {
+  name = "test-certs";
+  buildInputs = [ (minica.overrideAttrs (old: {
+    prePatch = ''
+      sed -i 's_NotAfter: time.Now().AddDate(2, 0, 30),_NotAfter: time.Now().AddDate(20, 0, 0),_' main.go
+    '';
+  })) ];
+  dontUnpack = true;
+
+  buildPhase = ''
+    minica \
+      --ca-key ca.key.pem \
+      --ca-cert ca.cert.pem \
+      --domains ${domain}
+  '';
+
+  installPhase = ''
+    mkdir -p $out
+    mv ca.*.pem $out/
+    mv ${domain}/key.pem $out/${domain}.key.pem
+    mv ${domain}/cert.pem $out/${domain}.cert.pem
+  '';
+}
diff --git a/nixpkgs/nixos/tests/common/acme/server/snakeoil-certs.nix b/nixpkgs/nixos/tests/common/acme/server/snakeoil-certs.nix
new file mode 100644
index 000000000000..11c3f7fc9290
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/acme/server/snakeoil-certs.nix
@@ -0,0 +1,13 @@
+let
+  domain = "acme.test";
+in {
+  inherit domain;
+  ca = {
+    cert = ./ca.cert.pem;
+    key = ./ca.key.pem;
+  };
+  "${domain}" = {
+    cert = ./. + "/${domain}.cert.pem";
+    key = ./. + "/${domain}.key.pem";
+  };
+}
diff --git a/nixpkgs/nixos/tests/common/auto-format-root-device.nix b/nixpkgs/nixos/tests/common/auto-format-root-device.nix
new file mode 100644
index 000000000000..fef8c7004991
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/auto-format-root-device.nix
@@ -0,0 +1,29 @@
+# This is a test utility that automatically formats
+# `config.virtualisation.rootDevice` in the initrd.
+# Note that when you are using
+# `boot.initrd.systemd.enable = true`, you can use
+# `virtualisation.fileSystems."/".autoFormat = true;`
+# instead.
+
+{ lib, config, pkgs, ... }:
+
+let
+  rootDevice = config.virtualisation.rootDevice;
+in
+{
+
+  boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
+    # We need mke2fs in the initrd.
+    copy_bin_and_libs ${pkgs.e2fsprogs}/bin/mke2fs
+  '';
+
+  boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
+    # If the disk image appears to be empty, run mke2fs to
+    # initialise.
+    FSTYPE=$(blkid -o value -s TYPE ${rootDevice} || true)
+    PARTTYPE=$(blkid -o value -s PTTYPE ${rootDevice} || true)
+    if test -z "$FSTYPE" -a -z "$PARTTYPE"; then
+        mke2fs -t ext4 ${rootDevice}
+    fi
+  '';
+}
diff --git a/nixpkgs/nixos/tests/common/auto.nix b/nixpkgs/nixos/tests/common/auto.nix
new file mode 100644
index 000000000000..ac56bed4a88f
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/auto.nix
@@ -0,0 +1,55 @@
+{ config, lib, ... }:
+
+let
+  dmcfg = config.services.xserver.displayManager;
+  cfg = config.test-support.displayManager.auto;
+in
+{
+
+  ###### interface
+
+  options = {
+    test-support.displayManager.auto = {
+      enable = lib.mkOption {
+        default = false;
+        description = lib.mdDoc ''
+          Whether to enable the fake "auto" display manager, which
+          automatically logs in the user specified in the
+          {option}`user` option.  This is mostly useful for
+          automated tests.
+        '';
+      };
+
+      user = lib.mkOption {
+        default = "root";
+        description = lib.mdDoc "The user account to login automatically.";
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = lib.mkIf cfg.enable {
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = cfg.user;
+      };
+    };
+
+    # lightdm by default doesn't allow auto login for root, which is
+    # required by some nixos tests. Override it here.
+    security.pam.services.lightdm-autologin.text = lib.mkForce ''
+        auth     requisite pam_nologin.so
+        auth     required  pam_succeed_if.so quiet
+        auth     required  pam_permit.so
+
+        account  include   lightdm
+
+        password include   lightdm
+
+        session  include   lightdm
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/common/ec2.nix b/nixpkgs/nixos/tests/common/ec2.nix
new file mode 100644
index 000000000000..1a64c464039b
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/ec2.nix
@@ -0,0 +1,73 @@
+{ pkgs, makeTest }:
+
+with pkgs.lib;
+
+{
+  makeEc2Test = { name, image, userData, script, hostname ? "ec2-instance", sshPublicKey ? null, meta ? {} }:
+    let
+      metaData = pkgs.stdenv.mkDerivation {
+        name = "metadata";
+        buildCommand = ''
+          mkdir -p $out/1.0/meta-data
+          ln -s ${pkgs.writeText "userData" userData} $out/1.0/user-data
+          echo "${hostname}" > $out/1.0/meta-data/hostname
+          echo "(unknown)" > $out/1.0/meta-data/ami-manifest-path
+        '' + optionalString (sshPublicKey != null) ''
+          mkdir -p $out/1.0/meta-data/public-keys/0
+          ln -s ${pkgs.writeText "sshPublicKey" sshPublicKey} $out/1.0/meta-data/public-keys/0/openssh-key
+        '';
+      };
+      indentLines = str: concatLines (map (s: "  " + s) (splitString "\n" str));
+    in makeTest {
+      name = "ec2-" + name;
+      nodes = {};
+      testScript = ''
+        import os
+        import subprocess
+        import tempfile
+
+        image_dir = os.path.join(
+            os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine"
+        )
+        os.makedirs(image_dir, mode=0o700, exist_ok=True)
+        disk_image = os.path.join(image_dir, "machine.qcow2")
+        subprocess.check_call(
+            [
+                "qemu-img",
+                "create",
+                "-f",
+                "qcow2",
+                "-F",
+                "qcow2",
+                "-o",
+                "backing_file=${image}",
+                disk_image,
+            ]
+        )
+        subprocess.check_call(["qemu-img", "resize", disk_image, "10G"])
+
+        # Note: we use net=169.0.0.0/8 rather than
+        # net=169.254.0.0/16 to prevent dhcpcd from getting horribly
+        # confused. (It would get a DHCP lease in the 169.254.*
+        # range, which it would then configure and promptly delete
+        # again when it deletes link-local addresses.) Ideally we'd
+        # turn off the DHCP server, but qemu does not have an option
+        # to do that.
+        start_command = (
+            "qemu-kvm -m 1024"
+            + " -device virtio-net-pci,netdev=vlan0"
+            + " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'"
+            + f" -drive file={disk_image},if=virtio,werror=report"
+            + " $QEMU_OPTS"
+        )
+
+        machine = create_machine({"startCommand": start_command})
+        try:
+      '' + indentLines script + ''
+        finally:
+          machine.shutdown()
+      '';
+
+      inherit meta;
+    };
+}
diff --git a/nixpkgs/nixos/tests/common/gpg-keyring.nix b/nixpkgs/nixos/tests/common/gpg-keyring.nix
new file mode 100644
index 000000000000..fb8d07b1183e
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/gpg-keyring.nix
@@ -0,0 +1,21 @@
+{ pkgs, ... }:
+
+pkgs.runCommand "gpg-keyring" { nativeBuildInputs = [ pkgs.gnupg ]; } ''
+  mkdir -p $out
+  export GNUPGHOME=$out
+  cat > foo <<EOF
+    %echo Generating a basic OpenPGP key
+    %no-protection
+    Key-Type: EdDSA
+    Key-Curve: ed25519
+    Name-Real: Bob Foobar
+    Name-Email: bob@foo.bar
+    Expire-Date: 0
+    # Do a commit here, so that we can later print "done"
+    %commit
+    %echo done
+  EOF
+  gpg --batch --generate-key foo
+  rm $out/S.gpg-agent $out/S.gpg-agent.*
+  gpg --export bob@foo.bar -a > $out/pubkey.gpg
+''
diff --git a/nixpkgs/nixos/tests/common/resolver.nix b/nixpkgs/nixos/tests/common/resolver.nix
new file mode 100644
index 000000000000..609058a7374a
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/resolver.nix
@@ -0,0 +1,141 @@
+# This module automatically discovers zones in BIND and NSD NixOS
+# configurations and creates zones for all definitions of networking.extraHosts
+# (except those that point to 127.0.0.1 or ::1) within the current test network
+# and delegates these zones using a fake root zone served by a BIND recursive
+# name server.
+{ config, nodes, pkgs, lib, ... }:
+
+{
+  options.test-support.resolver.enable = lib.mkOption {
+    type = lib.types.bool;
+    default = true;
+    internal = true;
+    description = lib.mdDoc ''
+      Whether to enable the resolver that automatically discovers zone in the
+      test network.
+
+      This option is `true` by default, because the module
+      defining this option needs to be explicitly imported.
+
+      The reason this option exists is for the
+      {file}`nixos/tests/common/acme/server` module, which
+      needs that option to disable the resolver once the user has set its own
+      resolver.
+    '';
+  };
+
+  config = lib.mkIf config.test-support.resolver.enable {
+    networking.firewall.enable = false;
+    services.bind.enable = true;
+    services.bind.cacheNetworks = lib.mkForce [ "any" ];
+    services.bind.forwarders = lib.mkForce [];
+    services.bind.zones = lib.singleton {
+      name = ".";
+      file = let
+        addDot = zone: zone + lib.optionalString (!lib.hasSuffix "." zone) ".";
+        mkNsdZoneNames = zones: map addDot (lib.attrNames zones);
+        mkBindZoneNames = zones: map (zone: addDot zone.name) zones;
+        getZones = cfg: mkNsdZoneNames cfg.services.nsd.zones
+                     ++ mkBindZoneNames cfg.services.bind.zones;
+
+        getZonesForNode = attrs: {
+          ip = attrs.config.networking.primaryIPAddress;
+          zones = lib.filter (zone: zone != ".") (getZones attrs.config);
+        };
+
+        zoneInfo = lib.mapAttrsToList (lib.const getZonesForNode) nodes;
+
+        # A and AAAA resource records for all the definitions of
+        # networking.extraHosts except those for 127.0.0.1 or ::1.
+        #
+        # The result is an attribute set with keys being the host name and the
+        # values are either { ipv4 = ADDR; } or { ipv6 = ADDR; } where ADDR is
+        # the IP address for the corresponding key.
+        recordsFromExtraHosts = let
+          getHostsForNode = lib.const (n: n.config.networking.extraHosts);
+          allHostsList = lib.mapAttrsToList getHostsForNode nodes;
+          allHosts = lib.concatStringsSep "\n" allHostsList;
+
+          reIp = "[a-fA-F0-9.:]+";
+          reHost = "[a-zA-Z0-9.-]+";
+
+          matchAliases = str: let
+            matched = builtins.match "[ \t]+(${reHost})(.*)" str;
+            continue = lib.singleton (lib.head matched)
+                    ++ matchAliases (lib.last matched);
+          in lib.optional (matched != null) continue;
+
+          matchLine = str: let
+            result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str;
+          in if result == null then null else {
+            ipAddr = lib.head result;
+            hosts = lib.singleton (lib.elemAt result 1)
+                 ++ matchAliases (lib.last result);
+          };
+
+          skipLine = str: let
+            rest = builtins.match "[^\n]*\n(.*)" str;
+          in if rest == null then "" else lib.head rest;
+
+          getEntries = str: acc: let
+            result = matchLine str;
+            next = getEntries (skipLine str);
+            newEntry = acc ++ lib.singleton result;
+            continue = if result == null then next acc else next newEntry;
+          in if str == "" then acc else continue;
+
+          isIPv6 = str: builtins.match ".*:.*" str != null;
+          loopbackIps = [ "127.0.0.1" "::1" ];
+          filterLoopback = lib.filter (e: !lib.elem e.ipAddr loopbackIps);
+
+          allEntries = lib.concatMap (entry: map (host: {
+            inherit host;
+            ${if isIPv6 entry.ipAddr then "ipv6" else "ipv4"} = entry.ipAddr;
+          }) entry.hosts) (filterLoopback (getEntries (allHosts + "\n") []));
+
+          mkRecords = entry: let
+            records = lib.optional (entry ? ipv6) "AAAA ${entry.ipv6}"
+                   ++ lib.optional (entry ? ipv4) "A ${entry.ipv4}";
+            mkRecord = typeAndData: "${entry.host}. IN ${typeAndData}";
+          in lib.concatMapStringsSep "\n" mkRecord records;
+
+        in lib.concatMapStringsSep "\n" mkRecords allEntries;
+
+        # All of the zones that are subdomains of existing zones.
+        # For example if there is only "example.com" the following zones would
+        # be 'subZones':
+        #
+        #  * foo.example.com.
+        #  * bar.example.com.
+        #
+        # While the following would *not* be 'subZones':
+        #
+        #  * example.com.
+        #  * com.
+        #
+        subZones = let
+          allZones = lib.concatMap (zi: zi.zones) zoneInfo;
+          isSubZoneOf = z1: z2: lib.hasSuffix z2 z1 && z1 != z2;
+        in lib.filter (z: lib.any (isSubZoneOf z) allZones) allZones;
+
+        # All the zones without 'subZones'.
+        filteredZoneInfo = map (zi: zi // {
+          zones = lib.filter (x: !lib.elem x subZones) zi.zones;
+        }) zoneInfo;
+
+      in pkgs.writeText "fake-root.zone" ''
+        $TTL 3600
+        . IN SOA ns.fakedns. admin.fakedns. ( 1 3h 1h 1w 1d )
+        ns.fakedns. IN A ${config.networking.primaryIPAddress}
+        . IN NS ns.fakedns.
+        ${lib.concatImapStrings (num: { ip, zones }: ''
+          ns${toString num}.fakedns. IN A ${ip}
+          ${lib.concatMapStrings (zone: ''
+          ${zone} IN NS ns${toString num}.fakedns.
+          '') zones}
+        '') (lib.filter (zi: zi.zones != []) filteredZoneInfo)}
+        ${recordsFromExtraHosts}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/common/user-account.nix b/nixpkgs/nixos/tests/common/user-account.nix
new file mode 100644
index 000000000000..a57ee2d59ae3
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/user-account.nix
@@ -0,0 +1,15 @@
+{ ... }:
+
+{ users.users.alice =
+    { isNormalUser = true;
+      description = "Alice Foobar";
+      password = "foobar";
+      uid = 1000;
+    };
+
+  users.users.bob =
+    { isNormalUser = true;
+      description = "Bob Foobar";
+      password = "foobar";
+    };
+}
diff --git a/nixpkgs/nixos/tests/common/wayland-cage.nix b/nixpkgs/nixos/tests/common/wayland-cage.nix
new file mode 100644
index 000000000000..fd0700941392
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/wayland-cage.nix
@@ -0,0 +1,13 @@
+{ ... }:
+
+{
+  imports = [ ./user-account.nix ];
+  services.cage = {
+    enable = true;
+    user = "alice";
+  };
+
+  virtualisation = {
+    qemu.options = [ "-vga virtio" ];
+  };
+}
diff --git a/nixpkgs/nixos/tests/common/webroot/news-rss.xml b/nixpkgs/nixos/tests/common/webroot/news-rss.xml
new file mode 100644
index 000000000000..b8099bf0364a
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/webroot/news-rss.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss xmlns:blogChannel="http://backend.userland.com/blogChannelModule" version="2.0">
+ <channel>
+  <title>NixOS News</title><link>https://nixos.org</link>
+  <description>News for NixOS, the purely functional Linux distribution.</description>
+  <image>
+   <title>NixOS</title>
+   <url>https://nixos.org/logo/nixos-logo-only-hires.png</url><link>https://nixos.org/</link>
+  </image>
+  <item>
+   <title>NixOS 18.09 released</title><link>https://nixos.org/news.html</link>
+   <description>
+    <a href="https://github.com/NixOS/nixos-artwork/blob/master/releases/18.09-jellyfish/jellyfish.png">
+     <img class="inline" src="logo/nixos-logo-18.09-jellyfish-lores.png" alt="18.09 Jellyfish logo" with="100" height="87"/>
+    </a>
+      NixOS 18.09 “Jellyfish†has been released, the tenth stable release branch.
+      See the <a href="/nixos/manual/release-notes.html#sec-release-18.09">release notes</a>
+      for details. You can get NixOS 18.09 ISOs and VirtualBox appliances
+      from the <a href="nixos/download.html">download page</a>.
+      For information on how to upgrade from older release branches
+      to 18.09, check out the
+      <a href="/nixos/manual/index.html#sec-upgrading">manual section on upgrading</a>.
+    </description>
+   <pubDate>Sat Oct 06 2018 00:00:00 GMT</pubDate>
+  </item>
+ </channel>
+</rss>
diff --git a/nixpkgs/nixos/tests/common/x11.nix b/nixpkgs/nixos/tests/common/x11.nix
new file mode 100644
index 000000000000..0d76a0e972ff
--- /dev/null
+++ b/nixpkgs/nixos/tests/common/x11.nix
@@ -0,0 +1,17 @@
+{ lib, ... }:
+
+{
+  imports = [
+    ./auto.nix
+  ];
+
+  services.xserver.enable = true;
+
+  # Automatically log in.
+  test-support.displayManager.auto.enable = true;
+
+  # Use IceWM as the window manager.
+  # Don't use a desktop manager.
+  services.xserver.displayManager.defaultSession = lib.mkDefault "none+icewm";
+  services.xserver.windowManager.icewm.enable = true;
+}
diff --git a/nixpkgs/nixos/tests/connman.nix b/nixpkgs/nixos/tests/connman.nix
new file mode 100644
index 000000000000..348b2a895a63
--- /dev/null
+++ b/nixpkgs/nixos/tests/connman.nix
@@ -0,0 +1,77 @@
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+{
+  name = "connman";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  # Router running radvd on VLAN 1
+  nodes.router = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    virtualisation.vlans = [ 1 ];
+
+    boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+
+    networking = {
+      useDHCP = false;
+      interfaces.eth1.ipv6.addresses =
+        [ { address = "fd12::1"; prefixLength = 64; } ];
+    };
+
+    services.radvd = {
+      enable = true;
+      config = ''
+        interface eth1 {
+          AdvSendAdvert on;
+          AdvManagedFlag on;
+          AdvOtherConfigFlag on;
+          prefix fd12::/64 {
+            AdvAutonomous off;
+          };
+        };
+      '';
+    };
+  };
+
+  # Client running connman, connected to VLAN 1
+  nodes.client = { ... }: {
+    virtualisation.vlans = [ 1 ];
+
+    # add a virtual wlan interface
+    boot.kernelModules = [ "mac80211_hwsim" ];
+    boot.extraModprobeConfig = ''
+      options mac80211_hwsim radios=1
+    '';
+
+    # Note: the overrides are needed because the wifi is
+    # disabled with mkVMOverride in qemu-vm.nix.
+    services.connman.enable = lib.mkOverride 0 true;
+    services.connman.networkInterfaceBlacklist = [ "eth0" ];
+    networking.wireless.enable = lib.mkOverride 0 true;
+    networking.wireless.interfaces = [ "wlan0" ];
+  };
+
+  testScript =
+    ''
+      start_all()
+
+      with subtest("Router is ready"):
+          router.wait_for_unit("radvd.service")
+
+      with subtest("Daemons are running"):
+          client.wait_for_unit("wpa_supplicant-wlan0.service")
+          client.wait_for_unit("connman.service")
+          client.wait_until_succeeds("connmanctl state | grep -q ready")
+
+      with subtest("Wired interface is configured"):
+          client.wait_until_succeeds("ip -6 route | grep -q fd12::/64")
+          client.wait_until_succeeds("ping -c 1 fd12::1")
+
+      with subtest("Can set up a wireless access point"):
+          client.succeed("connmanctl enable wifi")
+          client.wait_until_succeeds("connmanctl tether wifi on nixos-test reproducibility | grep -q 'Enabled'")
+          client.wait_until_succeeds("iw wlan0 info | grep -q nixos-test")
+    '';
+})
+
diff --git a/nixpkgs/nixos/tests/consul-template.nix b/nixpkgs/nixos/tests/consul-template.nix
new file mode 100644
index 000000000000..cbffa94569e3
--- /dev/null
+++ b/nixpkgs/nixos/tests/consul-template.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "consul-template";
+
+  nodes.machine = { ... }: {
+    services.consul-template.instances.example.settings = {
+      template = [{
+        contents = ''
+          {{ key "example" }}
+        '';
+        perms = "0600";
+        destination = "/example";
+      }];
+    };
+
+    services.consul = {
+      enable = true;
+      extraConfig = {
+        server = true;
+        bootstrap_expect = 1;
+        bind_addr = "127.0.0.1";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("consul.service")
+    machine.wait_for_open_port(8500)
+
+    machine.wait_for_unit("consul-template-example.service")
+
+    machine.wait_until_succeeds('consul kv put example example')
+
+    machine.wait_for_file("/example")
+    machine.succeed('grep "example" /example')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/consul.nix b/nixpkgs/nixos/tests/consul.nix
new file mode 100644
index 000000000000..6233234ff083
--- /dev/null
+++ b/nixpkgs/nixos/tests/consul.nix
@@ -0,0 +1,239 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+
+let
+  # Settings for both servers and agents
+  webUi = true;
+  retry_interval = "1s";
+  raft_multiplier = 1;
+
+  defaultExtraConfig = {
+    inherit retry_interval;
+    performance = {
+      inherit raft_multiplier;
+    };
+  };
+
+  allConsensusServerHosts = [
+    "192.168.1.1"
+    "192.168.1.2"
+    "192.168.1.3"
+  ];
+
+  allConsensusClientHosts = [
+    "192.168.2.1"
+    "192.168.2.2"
+  ];
+
+  firewallSettings = {
+    # See https://www.consul.io/docs/install/ports.html
+    allowedTCPPorts = [ 8301 8302 8600 8500 8300 ];
+    allowedUDPPorts = [ 8301 8302 8600 ];
+  };
+
+  client = index: { pkgs, ... }:
+    let
+      ip = builtins.elemAt allConsensusClientHosts index;
+    in
+      {
+        environment.systemPackages = [ pkgs.consul ];
+
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = ip; prefixLength = 16; }
+        ];
+        networking.firewall = firewallSettings;
+
+        services.consul = {
+          enable = true;
+          inherit webUi;
+          extraConfig = defaultExtraConfig // {
+            server = false;
+            retry_join = allConsensusServerHosts;
+            bind_addr = ip;
+          };
+        };
+      };
+
+  server = index: { pkgs, ... }:
+    let
+      numConsensusServers = builtins.length allConsensusServerHosts;
+      thisConsensusServerHost = builtins.elemAt allConsensusServerHosts index;
+      ip = thisConsensusServerHost; # since we already use IPs to identify servers
+    in
+      {
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = ip; prefixLength = 16; }
+        ];
+        networking.firewall = firewallSettings;
+
+        services.consul =
+          assert builtins.elem thisConsensusServerHost allConsensusServerHosts;
+          {
+            enable = true;
+            inherit webUi;
+            extraConfig = defaultExtraConfig // {
+              server = true;
+              bootstrap_expect = numConsensusServers;
+              # Tell Consul that we never intend to drop below this many servers.
+              # Ensures to not permanently lose consensus after temporary loss.
+              # See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
+              autopilot.min_quorum = numConsensusServers;
+              retry_join =
+                # If there's only 1 node in the network, we allow self-join;
+                # otherwise, the node must not try to join itself, and join only the other servers.
+                # See https://github.com/hashicorp/consul/issues/2868
+                if numConsensusServers == 1
+                  then allConsensusServerHosts
+                  else builtins.filter (h: h != thisConsensusServerHost) allConsensusServerHosts;
+              bind_addr = ip;
+            };
+          };
+      };
+in {
+  name = "consul";
+
+  nodes = {
+    server1 = server 0;
+    server2 = server 1;
+    server3 = server 2;
+
+    client1 = client 0;
+    client2 = client 1;
+  };
+
+  testScript = ''
+    servers = [server1, server2, server3]
+    machines = [server1, server2, server3, client1, client2]
+
+    for m in machines:
+        m.wait_for_unit("consul.service")
+
+
+    def wait_for_healthy_servers():
+        # See https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040
+        # for why the `Voter` column of `list-peers` has that info.
+        # TODO: The `grep true` relies on the fact that currently in
+        #       the output like
+        #           # consul operator raft list-peers
+        #           Node     ID   Address           State     Voter  RaftProtocol
+        #           server3  ...  192.168.1.3:8300  leader    true   3
+        #           server2  ...  192.168.1.2:8300  follower  true   3
+        #           server1  ...  192.168.1.1:8300  follower  false  3
+        #       `Voter`is the only boolean column.
+        #       Change this to the more reliable way to be defined by
+        #       https://github.com/hashicorp/consul/issues/8118
+        #       once that ticket is closed.
+        for m in machines:
+            m.wait_until_succeeds(
+                "[ $(consul operator raft list-peers | grep true | wc -l) == 3 ]"
+            )
+
+
+    def wait_for_all_machines_alive():
+        """
+        Note that Serf-"alive" does not mean "Raft"-healthy;
+        see `wait_for_healthy_servers()` for that instead.
+        """
+        for m in machines:
+            m.wait_until_succeeds("[ $(consul members | grep -o alive | wc -l) == 5 ]")
+
+
+    wait_for_healthy_servers()
+    # Also wait for clients to be alive.
+    wait_for_all_machines_alive()
+
+    client1.succeed("consul kv put testkey 42")
+    client2.succeed("[ $(consul kv get testkey) == 42 ]")
+
+
+    def rolling_restart_test(proper_rolling_procedure=True):
+        """
+        Tests that the cluster can tolearate failures of any single server,
+        following the recommended rolling upgrade procedure from
+        https://www.consul.io/docs/upgrading#standard-upgrades.
+
+        Optionally, `proper_rolling_procedure=False` can be given
+        to wait only for each server to be back `Healthy`, not `Stable`
+        in the Raft consensus, see Consul setting `ServerStabilizationTime` and
+        https://github.com/hashicorp/consul/issues/8118#issuecomment-645330040.
+        """
+
+        for server in servers:
+            server.block()
+            server.systemctl("stop consul")
+
+            # Make sure the stopped peer is recognized as being down
+            client1.wait_until_succeeds(
+              f"[ $(consul members | grep {server.name} | grep -o -E 'failed|left' | wc -l) == 1 ]"
+            )
+
+            # For each client, wait until they have connection again
+            # using `kv get -recurse` before issuing commands.
+            client1.wait_until_succeeds("consul kv get -recurse")
+            client2.wait_until_succeeds("consul kv get -recurse")
+
+            # Do some consul actions while one server is down.
+            client1.succeed("consul kv put testkey 43")
+            client2.succeed("[ $(consul kv get testkey) == 43 ]")
+            client2.succeed("consul kv delete testkey")
+
+            server.unblock()
+            server.systemctl("start consul")
+
+            if proper_rolling_procedure:
+                # Wait for recovery.
+                wait_for_healthy_servers()
+            else:
+                # NOT proper rolling upgrade procedure, see above.
+                wait_for_all_machines_alive()
+
+            # Wait for client connections.
+            client1.wait_until_succeeds("consul kv get -recurse")
+            client2.wait_until_succeeds("consul kv get -recurse")
+
+            # Do some consul actions with server back up.
+            client1.succeed("consul kv put testkey 44")
+            client2.succeed("[ $(consul kv get testkey) == 44 ]")
+            client2.succeed("consul kv delete testkey")
+
+
+    def all_servers_crash_simultaneously_test():
+        """
+        Tests that the cluster will eventually come back after all
+        servers crash simultaneously.
+        """
+
+        for server in servers:
+            server.block()
+            server.systemctl("stop --no-block consul")
+
+        for server in servers:
+            # --no-block is async, so ensure it has been stopped by now
+            server.wait_until_fails("systemctl is-active --quiet consul")
+            server.unblock()
+            server.systemctl("start consul")
+
+        # Wait for recovery.
+        wait_for_healthy_servers()
+
+        # Wait for client connections.
+        client1.wait_until_succeeds("consul kv get -recurse")
+        client2.wait_until_succeeds("consul kv get -recurse")
+
+        # Do some consul actions with servers back up.
+        client1.succeed("consul kv put testkey 44")
+        client2.succeed("[ $(consul kv get testkey) == 44 ]")
+        client2.succeed("consul kv delete testkey")
+
+
+    # Run the tests.
+
+    print("rolling_restart_test()")
+    rolling_restart_test()
+
+    print("all_servers_crash_simultaneously_test()")
+    all_servers_crash_simultaneously_test()
+
+    print("rolling_restart_test(proper_rolling_procedure=False)")
+    rolling_restart_test(proper_rolling_procedure=False)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-bridge.nix b/nixpkgs/nixos/tests/containers-bridge.nix
new file mode 100644
index 000000000000..d2e16299edaa
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-bridge.nix
@@ -0,0 +1,99 @@
+let
+  hostIp = "192.168.0.1";
+  containerIp = "192.168.0.100/24";
+  hostIp6 = "fc00::1";
+  containerIp6 = "fc00::2/7";
+in
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-bridge";
+  meta = {
+    maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+
+      networking.bridges = {
+        br0 = {
+          interfaces = [];
+        };
+      };
+      networking.interfaces = {
+        br0 = {
+          ipv4.addresses = [{ address = hostIp; prefixLength = 24; }];
+          ipv6.addresses = [{ address = hostIp6; prefixLength = 7; }];
+        };
+      };
+
+      containers.webserver =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          localAddress = containerIp;
+          localAddress6 = containerIp6;
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+      containers.web-noip =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit("default.target")
+    assert "webserver" in machine.succeed("nixos-container list")
+
+    with subtest("Start the webserver container"):
+        assert "up" in machine.succeed("nixos-container status webserver")
+
+    with subtest("Bridges exist inside containers"):
+        machine.succeed(
+            "nixos-container run webserver -- ip link show eth0",
+            "nixos-container run web-noip -- ip link show eth0",
+        )
+
+    ip = "${containerIp}".split("/")[0]
+    machine.succeed(f"ping -n -c 1 {ip}")
+    machine.succeed(f"curl --fail http://{ip}/ > /dev/null")
+
+    ip6 = "${containerIp6}".split("/")[0]
+    machine.succeed(f"ping -n -c 1 {ip6}")
+    machine.succeed(f"curl --fail http://[{ip6}]/ > /dev/null")
+
+    with subtest(
+        "nixos-container show-ip works in case of an ipv4 address "
+        + "with subnetmask in CIDR notation."
+    ):
+        result = machine.succeed("nixos-container show-ip webserver").rstrip()
+        assert result == ip
+
+    with subtest("Stop the container"):
+        machine.succeed("nixos-container stop webserver")
+        machine.fail(
+            f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null",
+            f"curl --fail --connect-timeout 2 http://[{ip6}]/ > /dev/null",
+        )
+
+    # Destroying a declarative container should fail.
+    machine.fail("nixos-container destroy webserver")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-custom-pkgs.nix b/nixpkgs/nixos/tests/containers-custom-pkgs.nix
new file mode 100644
index 000000000000..57184787c85f
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-custom-pkgs.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+
+  customPkgs = pkgs.appendOverlays [ (self: super: {
+    hello = super.hello.overrideAttrs (old: {
+       name = "custom-hello";
+    });
+  }) ];
+
+in {
+  name = "containers-custom-pkgs";
+  meta = {
+    maintainers = with lib.maintainers; [ erikarvstedt ];
+  };
+
+  nodes.machine = { config, ... }: {
+    assertions = let
+      helloName = (builtins.head config.containers.test.config.system.extraDependencies).name;
+    in [ {
+      assertion = helloName == "custom-hello";
+      message = "Unexpected value: ${helloName}";
+    } ];
+
+    containers.test = {
+      autoStart = true;
+      config = { pkgs, config, ... }: {
+        nixpkgs.pkgs = customPkgs;
+        system.extraDependencies = [ pkgs.hello ];
+      };
+    };
+  };
+
+  # This test only consists of evaluating the test machine
+  testScript = "pass";
+})
diff --git a/nixpkgs/nixos/tests/containers-ephemeral.nix b/nixpkgs/nixos/tests/containers-ephemeral.nix
new file mode 100644
index 000000000000..cb4b7d4eba0f
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-ephemeral.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-ephemeral";
+  meta = {
+    maintainers = with lib.maintainers; [ patryk27 ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.writableStore = true;
+
+    containers.webserver = {
+      ephemeral = true;
+      privateNetwork = true;
+      hostAddress = "10.231.136.1";
+      localAddress = "10.231.136.2";
+      config = {
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost = {
+            root = pkgs.runCommand "localhost" {} ''
+              mkdir "$out"
+              echo hello world > "$out/index.html"
+            '';
+          };
+        };
+        networking.firewall.allowedTCPPorts = [ 80 ];
+      };
+    };
+  };
+
+  testScript = ''
+    assert "webserver" in machine.succeed("nixos-container list")
+
+    machine.succeed("nixos-container start webserver")
+
+    with subtest("Container got its own root folder"):
+        machine.succeed("ls /run/nixos-containers/webserver")
+
+    with subtest("Container persistent directory is not created"):
+        machine.fail("ls /var/lib/nixos-containers/webserver")
+
+    # Since "start" returns after the container has reached
+    # multi-user.target, we should now be able to access it.
+    ip = machine.succeed("nixos-container show-ip webserver").rstrip()
+    machine.succeed(f"ping -n -c1 {ip}")
+    machine.succeed(f"curl --fail http://{ip}/ > /dev/null")
+
+    with subtest("Stop the container"):
+        machine.succeed("nixos-container stop webserver")
+        machine.fail(f"curl --fail --connect-timeout 2 http://{ip}/ > /dev/null")
+
+    with subtest("Container's root folder was removed"):
+        machine.fail("ls /run/nixos-containers/webserver")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-extra_veth.nix b/nixpkgs/nixos/tests/containers-extra_veth.nix
new file mode 100644
index 000000000000..f3e62265f6c4
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-extra_veth.nix
@@ -0,0 +1,91 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-extra_veth";
+  meta = {
+    maintainers = with lib.maintainers; [ kampfschlaefer ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+      virtualisation.vlans = [];
+
+      networking.useDHCP = false;
+      networking.bridges = {
+        br0 = {
+          interfaces = [];
+        };
+        br1 = { interfaces = []; };
+      };
+      networking.interfaces = {
+        br0 = {
+          ipv4.addresses = [{ address = "192.168.0.1"; prefixLength = 24; }];
+          ipv6.addresses = [{ address = "fc00::1"; prefixLength = 7; }];
+        };
+        br1 = {
+          ipv4.addresses = [{ address = "192.168.1.1"; prefixLength = 24; }];
+        };
+      };
+
+      containers.webserver =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          localAddress = "192.168.0.100/24";
+          localAddress6 = "fc00::2/7";
+          extraVeths = {
+            veth1 = { hostBridge = "br1"; localAddress = "192.168.1.100/24"; };
+            veth2 = { hostAddress = "192.168.2.1"; localAddress = "192.168.2.100"; };
+          };
+          config =
+            {
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      machine.wait_for_unit("default.target")
+      assert "webserver" in machine.succeed("nixos-container list")
+
+      with subtest("Status of the webserver container is up"):
+          assert "up" in machine.succeed("nixos-container status webserver")
+
+      with subtest("Ensure that the veths are inside the container"):
+          assert "state UP" in machine.succeed(
+              "nixos-container run webserver -- ip link show veth1"
+          )
+          assert "state UP" in machine.succeed(
+              "nixos-container run webserver -- ip link show veth2"
+          )
+
+      with subtest("Ensure the presence of the extra veths"):
+          assert "state UP" in machine.succeed("ip link show veth1")
+          assert "state UP" in machine.succeed("ip link show veth2")
+
+      with subtest("Ensure the veth1 is part of br1 on the host"):
+          assert "master br1" in machine.succeed("ip link show veth1")
+
+      with subtest("Ping on main veth"):
+          machine.succeed("ping -n -c 1 192.168.0.100")
+          machine.succeed("ping -n -c 1 fc00::2")
+
+      with subtest("Ping on the first extra veth"):
+          machine.succeed("ping -n -c 1 192.168.1.100 >&2")
+
+      with subtest("Ping on the second extra veth"):
+          machine.succeed("ping -n -c 1 192.168.2.100 >&2")
+
+      with subtest("Container can be stopped"):
+          machine.succeed("nixos-container stop webserver")
+          machine.fail("ping -n -c 1 192.168.1.100 >&2")
+          machine.fail("ping -n -c 1 192.168.2.100 >&2")
+
+      with subtest("Destroying a declarative container should fail"):
+          machine.fail("nixos-container destroy webserver")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/containers-hosts.nix b/nixpkgs/nixos/tests/containers-hosts.nix
new file mode 100644
index 000000000000..7bce7c997efe
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-hosts.nix
@@ -0,0 +1,49 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-hosts";
+  meta = {
+    maintainers = with lib.maintainers; [ montag451 ];
+  };
+
+  nodes.machine =
+    { lib, ... }:
+    {
+      virtualisation.vlans = [];
+
+      networking.bridges.br0.interfaces = [];
+      networking.interfaces.br0.ipv4.addresses = [
+        { address = "10.11.0.254"; prefixLength = 24; }
+      ];
+
+      # Force /etc/hosts to be the only source for host name resolution
+      environment.etc."nsswitch.conf".text = lib.mkForce ''
+        hosts: files
+      '';
+
+      containers.simple = {
+        autoStart = true;
+        privateNetwork = true;
+        localAddress = "10.10.0.1";
+        hostAddress = "10.10.0.254";
+
+        config = {};
+      };
+
+      containers.netmask = {
+        autoStart = true;
+        privateNetwork = true;
+        hostBridge = "br0";
+        localAddress = "10.11.0.1/24";
+
+        config = {};
+      };
+    };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("default.target")
+
+    with subtest("Ping the containers using the entries added in /etc/hosts"):
+        for host in "simple.containers", "netmask.containers":
+            machine.succeed(f"ping -n -c 1 {host}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-imperative.nix b/nixpkgs/nixos/tests/containers-imperative.nix
new file mode 100644
index 000000000000..fff00e4f73a8
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-imperative.nix
@@ -0,0 +1,170 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-imperative";
+  meta = {
+    maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
+  };
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+
+      # XXX: Sandbox setup fails while trying to hardlink files from the host's
+      #      store file system into the prepared chroot directory.
+      nix.settings.sandbox = false;
+      nix.settings.substituters = []; # don't try to access cache.nixos.org
+
+      virtualisation.memorySize = 2048;
+      virtualisation.writableStore = true;
+      # Make sure we always have all the required dependencies for creating a
+      # container available within the VM, because we don't have network access.
+      virtualisation.additionalPaths = let
+        emptyContainer = import ../lib/eval-config.nix {
+          modules = lib.singleton {
+            nixpkgs = { inherit (config.nixpkgs) localSystem; };
+
+            containers.foo.config = {};
+          };
+
+          # The system is inherited from the host above.
+          # Set it to null, to remove the "legacy" entrypoint's non-hermetic default.
+          system = null;
+        };
+      in with pkgs; [
+        stdenv stdenvNoCC emptyContainer.config.containers.foo.path
+        libxslt desktop-file-utils texinfo docbook5 libxml2
+        docbook_xsl_ns xorg.lndir documentation-highlighter
+      ];
+    };
+
+  testScript = let
+      tmpfilesContainerConfig = pkgs.writeText "container-config-tmpfiles" ''
+        {
+          systemd.tmpfiles.rules = [ "d /foo - - - - -" ];
+          systemd.services.foo = {
+            serviceConfig.Type = "oneshot";
+            script = "ls -al /foo";
+            wantedBy = [ "multi-user.target" ];
+          };
+        }
+      '';
+      brokenCfg = pkgs.writeText "broken.nix" ''
+        {
+          assertions = [
+            { assertion = false;
+              message = "I never evaluate";
+            }
+          ];
+        }
+      '';
+    in ''
+      with subtest("Make sure we have a NixOS tree (required by ‘nixos-container create’)"):
+          machine.succeed("PAGER=cat nix-env -qa -A nixos.hello >&2")
+
+      id1, id2 = None, None
+
+      with subtest("Create some containers imperatively"):
+          id1 = machine.succeed("nixos-container create foo --ensure-unique-name").rstrip()
+          machine.log(f"created container {id1}")
+
+          id2 = machine.succeed("nixos-container create foo --ensure-unique-name").rstrip()
+          machine.log(f"created container {id2}")
+
+          assert id1 != id2
+
+      with subtest(f"Put the root of {id2} into a bind mount"):
+          machine.succeed(
+              f"mv /var/lib/nixos-containers/{id2} /id2-bindmount",
+              f"mount --bind /id2-bindmount /var/lib/nixos-containers/{id1}",
+          )
+
+          ip1 = machine.succeed(f"nixos-container show-ip {id1}").rstrip()
+          ip2 = machine.succeed(f"nixos-container show-ip {id2}").rstrip()
+          assert ip1 != ip2
+
+      with subtest(
+          "Create a directory and a file we can later check if it still exists "
+          + "after destruction of the container"
+      ):
+          machine.succeed("mkdir /nested-bindmount")
+          machine.succeed("echo important data > /nested-bindmount/dummy")
+
+      with subtest(
+          "Create a directory with a dummy file and bind-mount it into both containers."
+      ):
+          for id in id1, id2:
+              important_path = f"/var/lib/nixos-containers/{id}/very/important/data"
+              machine.succeed(
+                  f"mkdir -p {important_path}",
+                  f"mount --bind /nested-bindmount {important_path}",
+              )
+
+      with subtest("Start one of them"):
+          machine.succeed(f"nixos-container start {id1}")
+
+      with subtest("Execute commands via the root shell"):
+          assert "Linux" in machine.succeed(f"nixos-container run {id1} -- uname")
+
+      with subtest("Execute a nix command via the root shell. (regression test for #40355)"):
+          machine.succeed(
+              f"nixos-container run {id1} -- nix-instantiate -E "
+              + '\'derivation { name = "empty"; builder = "false"; system = "false"; }\' '
+          )
+
+      with subtest("Stop and start (regression test for #4989)"):
+          machine.succeed(f"nixos-container stop {id1}")
+          machine.succeed(f"nixos-container start {id1}")
+
+      # clear serial backlog for next tests
+      machine.succeed("logger eat console backlog 3ea46eb2-7f82-4f70-b810-3f00e3dd4c4d")
+      machine.wait_for_console_text(
+          "eat console backlog 3ea46eb2-7f82-4f70-b810-3f00e3dd4c4d"
+      )
+
+      with subtest("Stop a container early"):
+          machine.succeed(f"nixos-container stop {id1}")
+          machine.succeed(f"nixos-container start {id1} >&2 &")
+          machine.wait_for_console_text("Stage 2")
+          machine.succeed(f"nixos-container stop {id1}")
+          machine.wait_for_console_text(f"Container {id1} exited successfully")
+          machine.succeed(f"nixos-container start {id1}")
+
+      with subtest("Stop a container without machined (regression test for #109695)"):
+          machine.systemctl("stop systemd-machined")
+          machine.succeed(f"nixos-container stop {id1}")
+          machine.wait_for_console_text(f"Container {id1} has been shut down")
+          machine.succeed(f"nixos-container start {id1}")
+
+      with subtest("tmpfiles are present"):
+          machine.log("creating container tmpfiles")
+          machine.succeed(
+              "nixos-container create tmpfiles --config-file ${tmpfilesContainerConfig}"
+          )
+          machine.log("created, starting…")
+          machine.succeed("nixos-container start tmpfiles")
+          machine.log("done starting, investigating…")
+          machine.succeed(
+              "echo $(nixos-container run tmpfiles -- systemctl is-active foo.service) | grep -q active;"
+          )
+          machine.succeed("nixos-container destroy tmpfiles")
+
+      with subtest("Execute commands via the root shell"):
+          assert "Linux" in machine.succeed(f"nixos-container run {id1} -- uname")
+
+      with subtest("Destroy the containers"):
+          for id in id1, id2:
+              machine.succeed(f"nixos-container destroy {id}")
+
+      with subtest("Check whether destruction of any container has killed important data"):
+          machine.succeed("grep -qF 'important data' /nested-bindmount/dummy")
+
+      with subtest("Ensure that the container path is gone"):
+          print(machine.succeed("ls -lsa /var/lib/nixos-containers"))
+          machine.succeed(f"test ! -e /var/lib/nixos-containers/{id1}")
+
+      with subtest("Ensure that a failed container creation doesn'leave any state"):
+          machine.fail(
+              "nixos-container create b0rk --config-file ${brokenCfg}"
+          )
+          machine.succeed("test ! -e /var/lib/nixos-containers/b0rk")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/containers-ip.nix b/nixpkgs/nixos/tests/containers-ip.nix
new file mode 100644
index 000000000000..ecff99a3f0c2
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-ip.nix
@@ -0,0 +1,71 @@
+let
+  webserverFor = hostAddress: localAddress: {
+    inherit hostAddress localAddress;
+    privateNetwork = true;
+    config = {
+      services.httpd = {
+        enable = true;
+        adminAddr = "foo@example.org";
+      };
+      networking.firewall.allowedTCPPorts = [ 80 ];
+    };
+  };
+
+in import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-ipv4-ipv6";
+  meta = {
+    maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }: {
+      virtualisation.writableStore = true;
+
+      containers.webserver4 = webserverFor "10.231.136.1" "10.231.136.2";
+      containers.webserver6 = webserverFor "fc00::2" "fc00::1";
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
+    };
+
+  testScript = { nodes, ... }: ''
+    import time
+
+
+    def curl_host(ip):
+        # put [] around ipv6 addresses for curl
+        host = ip if ":" not in ip else f"[{ip}]"
+        return f"curl --fail --connect-timeout 2 http://{host}/ > /dev/null"
+
+
+    def get_ip(container):
+        # need to distinguish because show-ip won't work for ipv6
+        if container == "webserver4":
+            ip = machine.succeed(f"nixos-container show-ip {container}").rstrip()
+            assert ip == "${nodes.machine.config.containers.webserver4.localAddress}"
+            return ip
+        return "${nodes.machine.config.containers.webserver6.localAddress}"
+
+
+    for container in "webserver4", "webserver6":
+        assert container in machine.succeed("nixos-container list")
+
+        with subtest(f"Start container {container}"):
+            machine.succeed(f"nixos-container start {container}")
+            # wait 2s for container to start and network to be up
+            time.sleep(2)
+
+        # Since "start" returns after the container has reached
+        # multi-user.target, we should now be able to access it.
+
+        ip = get_ip(container)
+        with subtest(f"{container} reacts to pings and HTTP requests"):
+            machine.succeed(f"ping -n -c1 {ip}")
+            machine.succeed(curl_host(ip))
+
+        with subtest(f"Stop container {container}"):
+            machine.succeed(f"nixos-container stop {container}")
+            machine.fail(curl_host(ip))
+
+        # Destroying a declarative container should fail.
+        machine.fail(f"nixos-container destroy {container}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-macvlans.nix b/nixpkgs/nixos/tests/containers-macvlans.nix
new file mode 100644
index 000000000000..a0cea8db4a1a
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-macvlans.nix
@@ -0,0 +1,82 @@
+let
+  # containers IP on VLAN 1
+  containerIp1 = "192.168.1.253";
+  containerIp2 = "192.168.1.254";
+in
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-macvlans";
+  meta = {
+    maintainers = with lib.maintainers; [ montag451 ];
+  };
+
+  nodes = {
+
+    machine1 =
+      { lib, ... }:
+      {
+        virtualisation.vlans = [ 1 ];
+
+        # To be able to ping containers from the host, it is necessary
+        # to create a macvlan on the host on the VLAN 1 network.
+        networking.macvlans.mv-eth1-host = {
+          interface = "eth1";
+          mode = "bridge";
+        };
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [];
+        networking.interfaces.mv-eth1-host = {
+          ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+        };
+
+        containers.test1 = {
+          autoStart = true;
+          macvlans = [ "eth1" ];
+
+          config = {
+            networking.interfaces.mv-eth1 = {
+              ipv4.addresses = [ { address = containerIp1; prefixLength = 24; } ];
+            };
+          };
+        };
+
+        containers.test2 = {
+          autoStart = true;
+          macvlans = [ "eth1" ];
+
+          config = {
+            networking.interfaces.mv-eth1 = {
+              ipv4.addresses = [ { address = containerIp2; prefixLength = 24; } ];
+            };
+          };
+        };
+      };
+
+    machine2 =
+      { ... }:
+      {
+        virtualisation.vlans = [ 1 ];
+      };
+
+  };
+
+  testScript = ''
+    start_all()
+    machine1.wait_for_unit("default.target")
+    machine2.wait_for_unit("default.target")
+
+    with subtest(
+        "Ping between containers to check that macvlans are created in bridge mode"
+    ):
+        machine1.succeed("nixos-container run test1 -- ping -n -c 1 ${containerIp2}")
+
+    with subtest("Ping containers from the host (machine1)"):
+        machine1.succeed("ping -n -c 1 ${containerIp1}")
+        machine1.succeed("ping -n -c 1 ${containerIp2}")
+
+    with subtest(
+        "Ping containers from the second machine to check that containers are reachable from the outside"
+    ):
+        machine2.succeed("ping -n -c 1 ${containerIp1}")
+        machine2.succeed("ping -n -c 1 ${containerIp2}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-names.nix b/nixpkgs/nixos/tests/containers-names.nix
new file mode 100644
index 000000000000..721f64990724
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-names.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-names";
+  meta = {
+    maintainers = with lib.maintainers; [ patryk27 ];
+  };
+
+  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;
+
+    containers = let
+      container = subnet: {
+        autoStart = true;
+        privateNetwork = true;
+        hostAddress = "192.168.${subnet}.1";
+        localAddress = "192.168.${subnet}.2";
+        config = { };
+      };
+
+     in {
+      first = container "1";
+      second = container "2";
+      really-long-name = container "3";
+      really-long-long-name-2 = container "4";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("default.target")
+
+    machine.succeed("ip link show | grep ve-first")
+    machine.succeed("ip link show | grep ve-second")
+    machine.succeed("ip link show | grep ve-really-lFYWO")
+    machine.succeed("ip link show | grep ve-really-l3QgY")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-nested.nix b/nixpkgs/nixos/tests/containers-nested.nix
new file mode 100644
index 000000000000..4a9fb8f01e24
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-nested.nix
@@ -0,0 +1,30 @@
+# Test for NixOS' container nesting.
+
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nested";
+
+  meta = with pkgs.lib.maintainers; { maintainers = [ sorki ]; };
+
+  nodes.machine = { lib, ... }:
+    let
+      makeNested = subConf: {
+        containers.nested = {
+          autoStart = true;
+          privateNetwork = true;
+          config = subConf;
+        };
+      };
+    in makeNested (makeNested { });
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("container@nested.service")
+    machine.succeed("systemd-run --pty --machine=nested -- machinectl list | grep nested")
+    print(
+        machine.succeed(
+            "systemd-run --pty --machine=nested -- systemd-run --pty --machine=nested -- systemctl status"
+        )
+    )
+  '';
+})
+
diff --git a/nixpkgs/nixos/tests/containers-physical_interfaces.nix b/nixpkgs/nixos/tests/containers-physical_interfaces.nix
new file mode 100644
index 000000000000..e203f88786a3
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-physical_interfaces.nix
@@ -0,0 +1,131 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-physical_interfaces";
+  meta = {
+    maintainers = with lib.maintainers; [ kampfschlaefer ];
+  };
+
+  nodes = {
+    server = { ... }:
+      {
+        virtualisation.vlans = [ 1 ];
+
+        containers.server = {
+          privateNetwork = true;
+          interfaces = [ "eth1" ];
+
+          config = {
+            networking.interfaces.eth1.ipv4.addresses = [
+              { address = "10.10.0.1"; prefixLength = 24; }
+            ];
+            networking.firewall.enable = false;
+          };
+        };
+      };
+    bridged = { ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      containers.bridged = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bridges.br0.interfaces = [ "eth1" ];
+          networking.interfaces.br0.ipv4.addresses = [
+            { address = "10.10.0.2"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+
+    bonded = { ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      containers.bonded = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bonds.bond0 = {
+            interfaces = [ "eth1" ];
+            driverOptions.mode = "active-backup";
+          };
+          networking.interfaces.bond0.ipv4.addresses = [
+            { address = "10.10.0.3"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+
+    bridgedbond = { ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      containers.bridgedbond = {
+        privateNetwork = true;
+        interfaces = [ "eth1" ];
+
+        config = {
+          networking.bonds.bond0 = {
+            interfaces = [ "eth1" ];
+            driverOptions.mode = "active-backup";
+          };
+          networking.bridges.br0.interfaces = [ "bond0" ];
+          networking.interfaces.br0.ipv4.addresses = [
+            { address = "10.10.0.4"; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("Prepare server"):
+        server.wait_for_unit("default.target")
+        server.succeed("ip link show dev eth1 >&2")
+
+    with subtest("Simple physical interface is up"):
+        server.succeed("nixos-container start server")
+        server.wait_for_unit("container@server")
+        server.succeed(
+            "systemctl -M server list-dependencies network-addresses-eth1.service >&2"
+        )
+
+        # The other tests will ping this container on its ip. Here we just check
+        # that the device is present in the container.
+        server.succeed("nixos-container run server -- ip a show dev eth1 >&2")
+
+    with subtest("Physical device in bridge in container can ping server"):
+        bridged.wait_for_unit("default.target")
+        bridged.succeed("nixos-container start bridged")
+        bridged.wait_for_unit("container@bridged")
+        bridged.succeed(
+            "systemctl -M bridged list-dependencies network-addresses-br0.service >&2",
+            "systemctl -M bridged status -n 30 -l network-addresses-br0.service",
+            "nixos-container run bridged -- ping -w 10 -c 1 -n 10.10.0.1",
+        )
+
+    with subtest("Physical device in bond in container can ping server"):
+        bonded.wait_for_unit("default.target")
+        bonded.succeed("nixos-container start bonded")
+        bonded.wait_for_unit("container@bonded")
+        bonded.succeed(
+            "systemctl -M bonded list-dependencies network-addresses-bond0 >&2",
+            "systemctl -M bonded status -n 30 -l network-addresses-bond0 >&2",
+            "nixos-container run bonded -- ping -w 10 -c 1 -n 10.10.0.1",
+        )
+
+    with subtest("Physical device in bond in bridge in container can ping server"):
+        bridgedbond.wait_for_unit("default.target")
+        bridgedbond.succeed("nixos-container start bridgedbond")
+        bridgedbond.wait_for_unit("container@bridgedbond")
+        bridgedbond.succeed(
+            "systemctl -M bridgedbond list-dependencies network-addresses-br0.service >&2",
+            "systemctl -M bridgedbond status -n 30 -l network-addresses-br0.service",
+            "nixos-container run bridgedbond -- ping -w 10 -c 1 -n 10.10.0.1",
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/containers-portforward.nix b/nixpkgs/nixos/tests/containers-portforward.nix
new file mode 100644
index 000000000000..b8c7aabc5a50
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-portforward.nix
@@ -0,0 +1,59 @@
+let
+  hostIp = "192.168.0.1";
+  hostPort = 10080;
+  containerIp = "192.168.0.100";
+  containerPort = 80;
+in
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-portforward";
+  meta = {
+    maintainers = with lib.maintainers; [ aristid aszlig eelco kampfschlaefer ianwookim ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+
+      containers.webserver =
+        { privateNetwork = true;
+          hostAddress = hostIp;
+          localAddress = containerIp;
+          forwardPorts = [ { protocol = "tcp"; hostPort = hostPort; containerPort = containerPort; } ];
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
+    };
+
+  testScript =
+    ''
+      container_list = machine.succeed("nixos-container list")
+      assert "webserver" in container_list
+
+      # Start the webserver container.
+      machine.succeed("nixos-container start webserver")
+
+      # wait two seconds for the container to start and the network to be up
+      machine.sleep(2)
+
+      # Since "start" returns after the container has reached
+      # multi-user.target, we should now be able to access it.
+      # ip = machine.succeed("nixos-container show-ip webserver").strip()
+      machine.succeed("ping -n -c1 ${hostIp}")
+      machine.succeed("curl --fail http://${hostIp}:${toString hostPort}/ > /dev/null")
+
+      # Stop the container.
+      machine.succeed("nixos-container stop webserver")
+      machine.fail("curl --fail --connect-timeout 2 http://${hostIp}:${toString hostPort}/ > /dev/null")
+
+      # Destroying a declarative container should fail.
+      machine.fail("nixos-container destroy webserver")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-reloadable.nix b/nixpkgs/nixos/tests/containers-reloadable.nix
new file mode 100644
index 000000000000..876e62c1da9e
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-reloadable.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  client_base = {
+    containers.test1 = {
+      autoStart = true;
+      config = {
+        environment.etc.check.text = "client_base";
+      };
+    };
+
+    # prevent make-test-python.nix to change IP
+    networking.interfaces = {
+      eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+    };
+  };
+in {
+  name = "containers-reloadable";
+  meta = {
+    maintainers = with lib.maintainers; [ danbst ];
+  };
+
+  nodes = {
+    client = { ... }: {
+      imports = [ client_base ];
+    };
+
+    client_c1 = { lib, ... }: {
+      imports = [ client_base ];
+
+      containers.test1.config = {
+        environment.etc.check.text = lib.mkForce "client_c1";
+        services.httpd.enable = true;
+        services.httpd.adminAddr = "nixos@example.com";
+      };
+    };
+    client_c2 = { lib, ... }: {
+      imports = [ client_base ];
+
+      containers.test1.config = {
+        environment.etc.check.text = lib.mkForce "client_c2";
+        services.nginx.enable = true;
+      };
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    c1System = nodes.client_c1.config.system.build.toplevel;
+    c2System = nodes.client_c2.config.system.build.toplevel;
+  in ''
+    client.start()
+    client.wait_for_unit("default.target")
+
+    assert "client_base" in client.succeed("nixos-container run test1 cat /etc/check")
+
+    with subtest("httpd is available after activating config1"):
+        client.succeed(
+            "${c1System}/bin/switch-to-configuration test >&2",
+            "[[ $(nixos-container run test1 cat /etc/check) == client_c1 ]] >&2",
+            "systemctl status httpd -M test1 >&2",
+        )
+
+    with subtest("httpd is not available any longer after switching to config2"):
+        client.succeed(
+            "${c2System}/bin/switch-to-configuration test >&2",
+            "[[ $(nixos-container run test1 cat /etc/check) == client_c2 ]] >&2",
+            "systemctl status nginx -M test1 >&2",
+        )
+        client.fail("systemctl status httpd -M test1 >&2")
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-restart_networking.nix b/nixpkgs/nixos/tests/containers-restart_networking.nix
new file mode 100644
index 000000000000..e1ad8157b288
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-restart_networking.nix
@@ -0,0 +1,113 @@
+let
+  client_base = {
+    networking.firewall.enable = false;
+
+    containers.webserver = {
+      autoStart = true;
+      privateNetwork = true;
+      hostBridge = "br0";
+      config = {
+        networking.firewall.enable = false;
+        networking.interfaces.eth0.ipv4.addresses = [
+          { address = "192.168.1.122"; prefixLength = 24; }
+        ];
+      };
+    };
+  };
+in import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "containers-restart_networking";
+  meta = {
+    maintainers = with lib.maintainers; [ kampfschlaefer ];
+  };
+
+  nodes = {
+    client = { lib, ... }: client_base // {
+      virtualisation.vlans = [ 1 ];
+
+      networking.bridges.br0 = {
+        interfaces = [];
+        rstp = false;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+      };
+
+    };
+    client_eth1 = { lib, ... }: client_base // {
+      networking.bridges.br0 = {
+        interfaces = [ "eth1" ];
+        rstp = false;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+    };
+    client_eth1_rstp = { lib, ... }: client_base // {
+      networking.bridges.br0 = {
+        interfaces = [ "eth1" ];
+        rstp = true;
+      };
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkOverride 0 [ ];
+        br0.ipv4.addresses =  [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    originalSystem = nodes.client.config.system.build.toplevel;
+    eth1_bridged = nodes.client_eth1.config.system.build.toplevel;
+    eth1_rstp = nodes.client_eth1_rstp.config.system.build.toplevel;
+  in ''
+    client.start()
+
+    client.wait_for_unit("default.target")
+
+    with subtest("Initial configuration connectivity check"):
+        client.succeed("ping 192.168.1.122 -c 1 -n >&2")
+        client.succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2")
+
+        client.fail("ip l show eth1 |grep 'master br0' >&2")
+        client.fail("grep eth1 /run/br0.interfaces >&2")
+
+    with subtest("Bridged configuration without STP preserves connectivity"):
+        client.succeed(
+            "${eth1_bridged}/bin/switch-to-configuration test >&2"
+        )
+
+        client.succeed(
+            "ping 192.168.1.122 -c 1 -n >&2",
+            "nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2",
+            "ip l show eth1 |grep 'master br0' >&2",
+            "grep eth1 /run/br0.interfaces >&2",
+        )
+
+    #  activating rstp needs another service, therefore the bridge will restart and the container will lose its connectivity
+    # with subtest("Bridged configuration with STP"):
+    #     client.succeed("${eth1_rstp}/bin/switch-to-configuration test >&2")
+    #     client.execute("ip -4 a >&2")
+    #     client.execute("ip l >&2")
+    #
+    #     client.succeed(
+    #         "ping 192.168.1.122 -c 1 -n >&2",
+    #         "nixos-container run webserver -- ping -c 1 -n 192.168.1.2 >&2",
+    #         "ip l show eth1 |grep 'master br0' >&2",
+    #         "grep eth1 /run/br0.interfaces >&2",
+    #     )
+
+    with subtest("Reverting to initial configuration preserves connectivity"):
+        client.succeed(
+            "${originalSystem}/bin/switch-to-configuration test >&2"
+        )
+
+        client.succeed("ping 192.168.1.122 -c 1 -n >&2")
+        client.succeed("nixos-container run webserver -- ping -c 1 -n 192.168.1.1 >&2")
+
+        client.fail("ip l show eth1 |grep 'master br0' >&2")
+        client.fail("grep eth1 /run/br0.interfaces >&2")
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/containers-tmpfs.nix b/nixpkgs/nixos/tests/containers-tmpfs.nix
new file mode 100644
index 000000000000..cf5b81656afe
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-tmpfs.nix
@@ -0,0 +1,90 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-tmpfs";
+  meta = {
+    maintainers = with lib.maintainers; [ patryk27 ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    { imports = [ ../modules/installer/cd-dvd/channel.nix ];
+      virtualisation.writableStore = true;
+
+      containers.tmpfs =
+        {
+          autoStart = true;
+          tmpfs = [
+            # Mount var as a tmpfs
+            "/var"
+
+            # Add a nested mount inside a tmpfs
+            "/var/log"
+
+            # Add a tmpfs on a path that does not exist
+            "/some/random/path"
+          ];
+          config = { };
+        };
+
+      virtualisation.additionalPaths = [ pkgs.stdenv ];
+    };
+
+  testScript = ''
+      machine.wait_for_unit("default.target")
+      assert "tmpfs" in machine.succeed("nixos-container list")
+
+      with subtest("tmpfs container is up"):
+          assert "up" in machine.succeed("nixos-container status tmpfs")
+
+
+      def tmpfs_cmd(command):
+          return f"nixos-container run tmpfs -- {command} 2>/dev/null"
+
+
+      with subtest("/var is mounted as a tmpfs"):
+          machine.succeed(tmpfs_cmd("mountpoint -q /var"))
+
+      with subtest("/var/log is mounted as a tmpfs"):
+          assert "What: tmpfs" in machine.succeed(
+              tmpfs_cmd("systemctl status var-log.mount --no-pager")
+          )
+          machine.succeed(tmpfs_cmd("mountpoint -q /var/log"))
+
+      with subtest("/some/random/path is mounted as a tmpfs"):
+          assert "What: tmpfs" in machine.succeed(
+              tmpfs_cmd("systemctl status some-random-path.mount --no-pager")
+          )
+          machine.succeed(tmpfs_cmd("mountpoint -q /some/random/path"))
+
+      with subtest(
+          "files created in the container in a non-tmpfs directory are visible on the host."
+      ):
+          # This establishes legitimacy for the following tests
+          machine.succeed(
+              tmpfs_cmd("touch /root/test.file"),
+              tmpfs_cmd("ls -l  /root | grep -q test.file"),
+              "test -e /var/lib/nixos-containers/tmpfs/root/test.file",
+          )
+
+      with subtest(
+          "/some/random/path is writable and that files created there are not "
+          + "in the hosts container dir but in the tmpfs"
+      ):
+          machine.succeed(
+              tmpfs_cmd("touch /some/random/path/test.file"),
+              tmpfs_cmd("test -e /some/random/path/test.file"),
+          )
+          machine.fail("test -e /var/lib/nixos-containers/tmpfs/some/random/path/test.file")
+
+      with subtest(
+          "files created in the hosts container dir in a path where a tmpfs "
+          + "file system has been mounted are not visible to the container as "
+          + "the do not exist in the tmpfs"
+      ):
+          machine.succeed(
+              "touch /var/lib/nixos-containers/tmpfs/var/test.file",
+              "test -e /var/lib/nixos-containers/tmpfs/var/test.file",
+              "ls -l /var/lib/nixos-containers/tmpfs/var/ | grep -q test.file 2>/dev/null",
+          )
+          machine.fail(tmpfs_cmd("ls -l /var | grep -q test.file"))
+    '';
+})
diff --git a/nixpkgs/nixos/tests/containers-unified-hierarchy.nix b/nixpkgs/nixos/tests/containers-unified-hierarchy.nix
new file mode 100644
index 000000000000..978d59e12c8a
--- /dev/null
+++ b/nixpkgs/nixos/tests/containers-unified-hierarchy.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "containers-unified-hierarchy";
+  meta = {
+    maintainers = with lib.maintainers; [ farnoy ];
+  };
+
+  nodes.machine = { ... }: {
+    containers = {
+      test-container = {
+        autoStart = true;
+        config = { };
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("default.target")
+
+    machine.succeed("echo 'stat -fc %T /sys/fs/cgroup/ | grep cgroup2fs' | nixos-container root-login test-container")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/convos.nix b/nixpkgs/nixos/tests/convos.nix
new file mode 100644
index 000000000000..06d8d30fcb14
--- /dev/null
+++ b/nixpkgs/nixos/tests/convos.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+
+let
+  port = 3333;
+in
+{
+  name = "convos";
+  meta.maintainers = with lib.maintainers; [ sgo ];
+
+  nodes = {
+    machine =
+      { pkgs, ... }:
+      {
+        services.convos = {
+          enable = true;
+          listenPort = port;
+        };
+      };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("convos")
+    machine.wait_for_open_port(${toString port})
+    machine.succeed("curl -f http://localhost:${toString port}/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/corerad.nix b/nixpkgs/nixos/tests/corerad.nix
new file mode 100644
index 000000000000..dd2bec794a1a
--- /dev/null
+++ b/nixpkgs/nixos/tests/corerad.nix
@@ -0,0 +1,92 @@
+import ./make-test-python.nix (
+  {
+    name = "corerad";
+    nodes = {
+      router = {config, pkgs, ...}: {
+        config = {
+          # This machine simulates a router with IPv6 forwarding and a static IPv6 address.
+          boot.kernel.sysctl = {
+            "net.ipv6.conf.all.forwarding" = true;
+          };
+          networking.interfaces.eth1 = {
+            ipv6.addresses = [ { address = "fd00:dead:beef:dead::1"; prefixLength = 64; } ];
+          };
+          services.corerad = {
+            enable = true;
+            # Serve router advertisements to the client machine with prefix information matching
+            # any IPv6 /64 prefixes configured on this interface.
+            #
+            # This configuration is identical to the example in the CoreRAD NixOS module.
+            settings = {
+              interfaces = [
+                {
+                  name = "eth0";
+                  monitor = true;
+                }
+                {
+                  name = "eth1";
+                  advertise = true;
+                  prefix = [{ prefix = "::/64"; }];
+                }
+              ];
+              debug = {
+                address = "localhost:9430";
+                prometheus = true;
+              };
+            };
+          };
+        };
+      };
+      client = {config, pkgs, ...}: {
+        # Use IPv6 SLAAC from router advertisements, and install rdisc6 so we can
+        # trigger one immediately.
+        config = {
+          boot.kernel.sysctl = {
+            "net.ipv6.conf.all.autoconf" = true;
+          };
+          environment.systemPackages = with pkgs; [
+            ndisc6
+          ];
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      with subtest("Wait for CoreRAD and network ready"):
+          # Ensure networking is online and CoreRAD is ready.
+          router.systemctl("start network-online.target")
+          client.systemctl("start network-online.target")
+          router.wait_for_unit("network-online.target")
+          client.wait_for_unit("network-online.target")
+          router.wait_for_unit("corerad.service")
+
+          # Ensure the client can reach the router.
+          client.wait_until_succeeds("ping -c 1 fd00:dead:beef:dead::1")
+
+      with subtest("Verify SLAAC on client"):
+          # Trigger a router solicitation and verify a SLAAC address is assigned from
+          # the prefix configured on the router.
+          client.wait_until_succeeds("rdisc6 -1 -r 10 eth1")
+          client.wait_until_succeeds(
+              "ip -6 addr show dev eth1 | grep -q 'fd00:dead:beef:dead:'"
+          )
+
+          addrs = client.succeed("ip -6 addr show dev eth1")
+
+          assert (
+              "fd00:dead:beef:dead:" in addrs
+          ), "SLAAC prefix was not found in client addresses after router advertisement"
+          assert (
+              "/64 scope global temporary" in addrs
+          ), "SLAAC temporary address was not configured on client after router advertisement"
+
+      with subtest("Verify HTTP debug server is configured"):
+          out = router.succeed("curl -f localhost:9430/metrics")
+
+          assert (
+              "corerad_build_info" in out
+          ), "Build info metric was not found in Prometheus output"
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/coturn.nix b/nixpkgs/nixos/tests/coturn.nix
new file mode 100644
index 000000000000..b44bf8d06e39
--- /dev/null
+++ b/nixpkgs/nixos/tests/coturn.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "coturn";
+  nodes = {
+    default = {
+      services.coturn.enable = true;
+    };
+    secretsfile = {
+      boot.postBootCommands = ''
+        echo "some-very-secret-string" > /run/coturn-secret
+      '';
+      services.coturn = {
+        enable = true;
+        static-auth-secret-file = "/run/coturn-secret";
+      };
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+
+      with subtest("by default works without configuration"):
+          default.wait_for_unit("coturn.service")
+
+      with subtest("works with static-auth-secret-file"):
+          secretsfile.wait_for_unit("coturn.service")
+          secretsfile.wait_for_open_port(3478)
+          secretsfile.succeed("grep 'some-very-secret-string' /run/coturn/turnserver.cfg")
+          # Forbidden IP, fails:
+          secretsfile.fail("${pkgs.coturn}/bin/turnutils_uclient -W some-very-secret-string 127.0.0.1 -DgX -e 127.0.0.1 -n 1 -c -y")
+          # allowed-peer-ip, should succeed:
+          secretsfile.succeed("${pkgs.coturn}/bin/turnutils_uclient -W some-very-secret-string 192.168.1.2 -DgX -e 192.168.1.2 -n 1 -c -y")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/couchdb.nix b/nixpkgs/nixos/tests/couchdb.nix
new file mode 100644
index 000000000000..cf6ca8e4548d
--- /dev/null
+++ b/nixpkgs/nixos/tests/couchdb.nix
@@ -0,0 +1,57 @@
+let
+  makeNode = couchpkg: user: passwd:
+    { pkgs, ... } :
+
+      { environment.systemPackages = [ pkgs.jq ];
+        services.couchdb.enable = true;
+        services.couchdb.package = couchpkg;
+        services.couchdb.adminUser = user;
+        services.couchdb.adminPass = passwd;
+      };
+  testuser = "testadmin";
+  testpass = "cowabunga";
+  testlogin = "${testuser}:${testpass}@";
+in
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+{
+  name = "couchdb";
+  meta.maintainers = [ ];
+
+  nodes = {
+    couchdb3 = makeNode pkgs.couchdb3 testuser testpass;
+  };
+
+  testScript = let
+    curlJqCheck = login: action: path: jqexpr: result:
+      pkgs.writeScript "curl-jq-check-${action}-${path}.sh" ''
+        RESULT=$(curl -X ${action} http://${login}127.0.0.1:5984/${path} | jq -r '${jqexpr}')
+        echo $RESULT >&2
+        if [ "$RESULT" != "${result}" ]; then
+          exit 1
+        fi
+      '';
+  in ''
+    start_all()
+
+    couchdb3.wait_for_unit("couchdb.service")
+    couchdb3.wait_until_succeeds(
+        "${curlJqCheck testlogin "GET" "" ".couchdb" "Welcome"}"
+    )
+    couchdb3.wait_until_succeeds(
+        "${curlJqCheck testlogin "GET" "_all_dbs" ". | length" "0"}"
+    )
+    couchdb3.succeed("${curlJqCheck testlogin "PUT" "foo" ".ok" "true"}")
+    couchdb3.succeed(
+        "${curlJqCheck testlogin "GET" "_all_dbs" ". | length" "1"}"
+    )
+    couchdb3.succeed(
+        "${curlJqCheck testlogin "DELETE" "foo" ".ok" "true"}"
+    )
+    couchdb3.succeed(
+        "${curlJqCheck testlogin "GET" "_all_dbs" ". | length" "0"}"
+    )
+    couchdb3.succeed(
+        "${curlJqCheck testlogin "GET" "_node/couchdb@127.0.0.1" ".couchdb" "Welcome"}"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/cri-o.nix b/nixpkgs/nixos/tests/cri-o.nix
new file mode 100644
index 000000000000..08e1e8f36b06
--- /dev/null
+++ b/nixpkgs/nixos/tests/cri-o.nix
@@ -0,0 +1,19 @@
+# This test runs CRI-O and verifies via critest
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "cri-o";
+  meta.maintainers = with pkgs.lib; teams.podman.members;
+
+  nodes = {
+    crio = {
+      virtualisation.cri-o.enable = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+    crio.wait_for_unit("crio.service")
+    crio.succeed(
+        "critest --ginkgo.focus='Runtime info' --runtime-endpoint unix:///var/run/crio/crio.sock"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/croc.nix b/nixpkgs/nixos/tests/croc.nix
new file mode 100644
index 000000000000..5d709eb3d1cb
--- /dev/null
+++ b/nixpkgs/nixos/tests/croc.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  client = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.croc ];
+  };
+  pass = pkgs.writeText "pass" "PassRelay";
+in {
+  name = "croc";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hax404 julm ];
+  };
+
+  nodes = {
+    relay = {
+      services.croc = {
+        enable = true;
+        pass = pass;
+        openFirewall = true;
+      };
+    };
+    sender = client;
+    receiver = client;
+  };
+
+  testScript = ''
+    start_all()
+
+    # wait until relay is up
+    relay.wait_for_unit("croc")
+    relay.wait_for_open_port(9009)
+    relay.wait_for_open_port(9010)
+    relay.wait_for_open_port(9011)
+    relay.wait_for_open_port(9012)
+    relay.wait_for_open_port(9013)
+
+    # generate testfiles and send them
+    sender.wait_for_unit("multi-user.target")
+    sender.execute("echo Hello World > testfile01.txt")
+    sender.execute("echo Hello Earth > testfile02.txt")
+    sender.execute(
+        "croc --pass ${pass} --relay relay send --code topSecret testfile01.txt testfile02.txt >&2 &"
+    )
+
+    # receive the testfiles and check them
+    receiver.succeed(
+        "croc --pass ${pass} --yes --relay relay topSecret"
+    )
+    assert "Hello World" in receiver.succeed("cat testfile01.txt")
+    assert "Hello Earth" in receiver.succeed("cat testfile02.txt")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/cups-pdf.nix b/nixpkgs/nixos/tests/cups-pdf.nix
new file mode 100644
index 000000000000..5602193b0408
--- /dev/null
+++ b/nixpkgs/nixos/tests/cups-pdf.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "cups-pdf";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    environment.systemPackages = [ pkgs.poppler_utils ];
+    fonts.packages = [ pkgs.dejavu_fonts ];  # yields more OCR-able pdf
+    services.printing.cups-pdf.enable = true;
+    services.printing.cups-pdf.instances = {
+      opt = {};
+      noopt.installPrinter = false;
+    };
+    hardware.printers.ensurePrinters = [{
+      name = "noopt";
+      model = "CUPS-PDF_noopt.ppd";
+      deviceUri = "cups-pdf:/noopt";
+    }];
+  };
+
+  # we cannot check the files with pdftotext, due to
+  # https://github.com/alexivkin/CUPS-PDF-to-PDF/issues/7
+  # we need `imagemagickBig` as it has ghostscript support
+
+  testScript = ''
+    from subprocess import run
+    machine.wait_for_unit("multi-user.target")
+    for name in ("opt", "noopt"):
+        text = f"test text {name}".upper()
+        machine.wait_until_succeeds(f"lpstat -v {name}")
+        machine.succeed(f"su - alice -c 'echo -e \"\n  {text}\" | lp -d {name}'")
+        # wait until the pdf files are completely produced and readable by alice
+        machine.wait_until_succeeds(f"su - alice -c 'pdfinfo /var/spool/cups-pdf-{name}/users/alice/*.pdf'")
+        machine.succeed(f"cp /var/spool/cups-pdf-{name}/users/alice/*.pdf /tmp/{name}.pdf")
+        machine.copy_from_vm(f"/tmp/{name}.pdf", "")
+        run(f"${pkgs.imagemagickBig}/bin/convert -density 300 $out/{name}.pdf $out/{name}.jpeg", shell=True, check=True)
+        assert text.encode() in run(f"${lib.getExe pkgs.tesseract} $out/{name}.jpeg stdout", shell=True, check=True, capture_output=True).stdout
+  '';
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+})
diff --git a/nixpkgs/nixos/tests/curl-impersonate.nix b/nixpkgs/nixos/tests/curl-impersonate.nix
new file mode 100644
index 000000000000..33b10da1dfd0
--- /dev/null
+++ b/nixpkgs/nixos/tests/curl-impersonate.nix
@@ -0,0 +1,159 @@
+/*
+  Test suite for curl-impersonate
+
+  Abstract:
+    Uses the test suite from the curl-impersonate source repo which:
+
+    1. Performs requests with libcurl and captures the TLS client-hello
+       packets with tcpdump to compare against known-good signatures
+    2. Spins up an nghttpd2 server to test client HTTP/2 headers against
+       known-good headers
+
+    See https://github.com/lwthiker/curl-impersonate/tree/main/tests/signatures
+    for details.
+
+  Notes:
+    - We need to have our own web server running because the tests expect to be able
+      to hit domains like wikipedia.org and the sandbox has no internet
+    - We need to be able to do (verifying) TLS handshakes without internet access.
+      We do that by creating a trusted CA and issuing a cert that includes
+      all of the test domains as subject-alternative names and then spoofs the
+      hostnames in /etc/hosts.
+*/
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+  # Update with domains in TestImpersonate.TEST_URLS if needed from:
+  # https://github.com/lwthiker/curl-impersonate/blob/main/tests/test_impersonate.py
+  domains = [
+    "www.wikimedia.org"
+    "www.wikipedia.org"
+    "www.mozilla.org"
+    "www.apache.org"
+    "www.kernel.org"
+    "git-scm.com"
+  ];
+
+  tls-certs = let
+    # Configure CA with X.509 v3 extensions that would be trusted by curl
+    ca-cert-conf = pkgs.writeText "curl-impersonate-ca.cnf" ''
+      basicConstraints = critical, CA:TRUE
+      subjectKeyIdentifier = hash
+      authorityKeyIdentifier = keyid:always, issuer:always
+      keyUsage = critical, cRLSign, digitalSignature, keyCertSign
+    '';
+
+    # Configure leaf certificate with X.509 v3 extensions that would be trusted
+    # by curl and set subject-alternative names for test domains
+    tls-cert-conf = pkgs.writeText "curl-impersonate-tls.cnf" ''
+      basicConstraints = critical, CA:FALSE
+      subjectKeyIdentifier = hash
+      authorityKeyIdentifier = keyid:always, issuer:always
+      keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement
+      extendedKeyUsage = critical, serverAuth
+      subjectAltName = @alt_names
+
+      [alt_names]
+      ${lib.concatStringsSep "\n" (lib.imap0 (idx: domain: "DNS.${toString idx} = ${domain}") domains)}
+    '';
+  in pkgs.runCommand "curl-impersonate-test-certs" {
+    nativeBuildInputs = [ pkgs.openssl ];
+  } ''
+    # create CA certificate and key
+    openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca-csr.pem -nodes -subj '/CN=curl-impersonate-ca.nixos.test'
+    openssl x509 -req -sha512 -in ca-csr.pem -key ca-key.pem -out ca.pem -extfile ${ca-cert-conf} -days 36500
+    openssl x509 -in ca.pem -text
+
+    # create server certificate and key
+    openssl req -newkey rsa:4096 -keyout key.pem -out csr.pem -nodes -subj '/CN=curl-impersonate.nixos.test'
+    openssl x509 -req -sha512 -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile ${tls-cert-conf} -days 36500
+    openssl x509 -in cert.pem -text
+
+    # output CA cert and server cert and key
+    mkdir -p $out
+    cp key.pem cert.pem ca.pem $out
+  '';
+
+  # Test script
+  curl-impersonate-test = let
+    # Build miniature libcurl client used by test driver
+    minicurl = pkgs.runCommandCC "minicurl" {
+      buildInputs = [ pkgs.curl ];
+    } ''
+      mkdir -p $out/bin
+      $CC -Wall -Werror -o $out/bin/minicurl ${pkgs.curl-impersonate.src}/tests/minicurl.c `curl-config --libs`
+    '';
+  in pkgs.writeShellScript "curl-impersonate-test" ''
+    set -euxo pipefail
+
+    # Test driver requirements
+    export PATH="${with pkgs; lib.makeBinPath [
+      bash
+      coreutils
+      python3Packages.pytest
+      nghttp2
+      tcpdump
+    ]}"
+    export PYTHONPATH="${with pkgs.python3Packages; makePythonPath [
+      pyyaml
+      pytest-asyncio
+      dpkt
+    ]}"
+
+    # Prepare test root prefix
+    mkdir -p usr/{bin,lib}
+    cp -rs ${pkgs.curl-impersonate}/* ${minicurl}/* usr/
+
+    cp -r ${pkgs.curl-impersonate.src}/tests ./
+
+    # Run tests
+    cd tests
+    pytest . --install-dir ../usr --capture-interface eth1
+  '';
+in {
+  name = "curl-impersonate";
+
+  meta = with lib.maintainers; {
+    maintainers = [ lilyinstarlight ];
+  };
+
+  nodes = {
+    web = { nodes, pkgs, lib, config, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      services = {
+        nginx = {
+          enable = true;
+          virtualHosts."curl-impersonate.nixos.test" = {
+            default = true;
+            addSSL = true;
+            sslCertificate = "${tls-certs}/cert.pem";
+            sslCertificateKey = "${tls-certs}/key.pem";
+          };
+        };
+      };
+    };
+
+    curl = { nodes, pkgs, lib, config, ... }: {
+      networking.extraHosts = lib.concatStringsSep "\n" (map (domain: "${nodes.web.networking.primaryIPAddress}  ${domain}") domains);
+
+      security.pki.certificateFiles = [ "${tls-certs}/ca.pem" ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    with subtest("Wait for network"):
+        web.systemctl("start network-online.target")
+        curl.systemctl("start network-online.target")
+        web.wait_for_unit("network-online.target")
+        curl.wait_for_unit("network-online.target")
+
+    with subtest("Wait for web server"):
+        web.wait_for_unit("nginx.service")
+        web.wait_for_open_port(443)
+
+    with subtest("Run curl-impersonate tests"):
+        curl.succeed("${curl-impersonate-test}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/custom-ca.nix b/nixpkgs/nixos/tests/custom-ca.nix
new file mode 100644
index 000000000000..0fcdf81022d7
--- /dev/null
+++ b/nixpkgs/nixos/tests/custom-ca.nix
@@ -0,0 +1,195 @@
+# Checks that `security.pki` options are working in curl and the main browser
+# engines: Gecko (via Firefox), Chromium, QtWebEngine (via qutebrowser) and
+# WebKitGTK (via Midori). The test checks that certificates issued by a custom
+# trusted CA are accepted but those from an unknown CA are rejected.
+
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (pkgs) lib;
+
+  makeCert = { caName, domain }: pkgs.runCommand "example-cert"
+  { buildInputs = [ pkgs.gnutls ]; }
+  ''
+    mkdir $out
+
+    # CA cert template
+    cat >ca.template <<EOF
+    organization = "${caName}"
+    cn = "${caName}"
+    expiration_days = 365
+    ca
+    cert_signing_key
+    crl_signing_key
+    EOF
+
+    # server cert template
+    cat >server.template <<EOF
+    organization = "An example company"
+    cn = "${domain}"
+    expiration_days = 30
+    dns_name = "${domain}"
+    encryption_key
+    signing_key
+    EOF
+
+    # generate CA keypair
+    certtool                \
+      --generate-privkey    \
+      --key-type rsa        \
+      --sec-param High      \
+      --outfile $out/ca.key
+    certtool                     \
+      --generate-self-signed     \
+      --load-privkey $out/ca.key \
+      --template ca.template     \
+      --outfile $out/ca.crt
+
+    # generate server keypair
+    certtool                    \
+      --generate-privkey        \
+      --key-type rsa            \
+      --sec-param High          \
+      --outfile $out/server.key
+    certtool                            \
+      --generate-certificate            \
+      --load-privkey $out/server.key    \
+      --load-ca-privkey $out/ca.key     \
+      --load-ca-certificate $out/ca.crt \
+      --template server.template        \
+      --outfile $out/server.crt
+  '';
+
+  example-good-cert = makeCert
+    { caName = "Example good CA";
+      domain = "good.example.com";
+    };
+
+  example-bad-cert = makeCert
+    { caName = "Unknown CA";
+      domain = "bad.example.com";
+    };
+
+  webserverConfig =
+    { networking.hosts."127.0.0.1" = [ "good.example.com" "bad.example.com" ];
+      security.pki.certificateFiles = [ "${example-good-cert}/ca.crt" ];
+
+      services.nginx.enable = true;
+      services.nginx.virtualHosts."good.example.com" =
+        { onlySSL = true;
+          sslCertificate = "${example-good-cert}/server.crt";
+          sslCertificateKey = "${example-good-cert}/server.key";
+          locations."/".extraConfig = ''
+            add_header Content-Type text/plain;
+            return 200 'It works!';
+          '';
+        };
+      services.nginx.virtualHosts."bad.example.com" =
+        { onlySSL = true;
+          sslCertificate = "${example-bad-cert}/server.crt";
+          sslCertificateKey = "${example-bad-cert}/server.key";
+          locations."/".extraConfig = ''
+            add_header Content-Type text/plain;
+            return 200 'It does not work!';
+          '';
+        };
+    };
+
+  curlTest = makeTest {
+    name = "custom-ca-curl";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+    nodes.machine = { ... }: webserverConfig;
+    testScript = ''
+        with subtest("Good certificate is trusted in curl"):
+            machine.wait_for_unit("nginx")
+            machine.wait_for_open_port(443)
+            machine.succeed("curl -fv https://good.example.com")
+
+        with subtest("Unknown CA is untrusted in curl"):
+            machine.fail("curl -fv https://bad.example.com")
+    '';
+  };
+
+  mkBrowserTest = browser: testParams: makeTest {
+    name = "custom-ca-${browser}";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+    enableOCR = true;
+
+    nodes.machine = { pkgs, ... }:
+      { imports =
+          [ ./common/user-account.nix
+            ./common/x11.nix
+            webserverConfig
+          ];
+
+        # chromium-based browsers refuse to run as root
+        test-support.displayManager.auto.user = "alice";
+
+        # machine often runs out of memory with less
+        virtualisation.memorySize = 1024;
+
+        environment.systemPackages = [ pkgs.xdotool pkgs.${browser} ];
+      };
+
+    testScript = ''
+      from typing import Tuple
+      def execute_as(user: str, cmd: str) -> Tuple[int, str]:
+          """
+          Run a shell command as a specific user.
+          """
+          return machine.execute(f"sudo -u {user} {cmd}")
+
+
+      def wait_for_window_as(user: str, cls: str) -> None:
+          """
+          Wait until a X11 window of a given user appears.
+          """
+
+          def window_is_visible(last_try: bool) -> bool:
+              ret, stdout = execute_as(user, f"xdotool search --onlyvisible --class {cls}")
+              if last_try:
+                  machine.log(f"Last chance to match {cls} on the window list")
+              return ret == 0
+
+          with machine.nested("Waiting for a window to appear"):
+              retry(window_is_visible)
+
+
+      machine.start()
+      machine.wait_for_x()
+
+      command = "${browser} ${testParams.args or ""}"
+      with subtest("Good certificate is trusted in ${browser}"):
+          execute_as(
+              "alice", f"{command} https://good.example.com >&2 &"
+          )
+          wait_for_window_as("alice", "${browser}")
+          machine.sleep(4)
+          execute_as("alice", "xdotool key ctrl+r")  # reload to be safe
+          machine.wait_for_text("It works!")
+          machine.screenshot("good${browser}")
+          execute_as("alice", "xdotool key ctrl+w")  # close tab
+
+      with subtest("Unknown CA is untrusted in ${browser}"):
+          execute_as("alice", f"{command} https://bad.example.com >&2 &")
+          machine.wait_for_text("${testParams.error}")
+          machine.screenshot("bad${browser}")
+    '';
+  };
+
+in
+
+{
+  curl = curlTest;
+} // pkgs.lib.mapAttrs mkBrowserTest {
+  firefox = { error = "Security Risk"; };
+  chromium = { error = "not private"; };
+  qutebrowser = { args = "-T"; error = "Certificate error"; };
+  midori = { args = "-p"; error = "Security"; };
+}
diff --git a/nixpkgs/nixos/tests/dae.nix b/nixpkgs/nixos/tests/dae.nix
new file mode 100644
index 000000000000..42a2eb5fe0be
--- /dev/null
+++ b/nixpkgs/nixos/tests/dae.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+
+  name = "dae";
+
+  meta = {
+    maintainers = with lib.maintainers; [ oluceps ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.nginx = {
+      enable = true;
+      statusPage = true;
+    };
+    services.dae = {
+      enable = true;
+      config = ''
+        global{}
+        routing{}
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_unit("dae.service")
+
+    machine.wait_for_open_port(80)
+
+    machine.succeed("curl --fail --max-time 10 http://localhost")
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/darling.nix b/nixpkgs/nixos/tests/darling.nix
new file mode 100644
index 000000000000..5665b4c2ffef
--- /dev/null
+++ b/nixpkgs/nixos/tests/darling.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  # Well, we _can_ cross-compile from Linux :)
+  hello = pkgs.runCommand "hello" {
+    sdk = "${pkgs.darling.sdk}/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk";
+    nativeBuildInputs = with pkgs.llvmPackages_14; [ clang-unwrapped lld ];
+    src = pkgs.writeText "hello.c" ''
+      #include <stdio.h>
+      int main() {
+        printf("Hello, Darling!\n");
+        return 0;
+      }
+    '';
+  } ''
+    clang \
+      -target x86_64-apple-darwin \
+      -fuse-ld=lld \
+      -nostdinc -nostdlib \
+      -mmacosx-version-min=10.15 \
+      --sysroot $sdk \
+      -isystem $sdk/usr/include \
+      -L $sdk/usr/lib -lSystem \
+      $src -o $out
+  '';
+in
+{
+  name = "darling";
+
+  meta.maintainers = with lib.maintainers; [ zhaofengli ];
+
+  nodes.machine = {
+    programs.darling.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    # Darling holds stdout until the server is shutdown
+    machine.succeed("darling ${hello} >hello.out")
+    machine.succeed("grep Hello hello.out")
+    machine.succeed("darling shutdown")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/dconf.nix b/nixpkgs/nixos/tests/dconf.nix
new file mode 100644
index 000000000000..192c075540a4
--- /dev/null
+++ b/nixpkgs/nixos/tests/dconf.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix
+  ({ lib, ... }:
+  {
+    name = "dconf";
+
+    meta.maintainers = with lib.maintainers; [
+      linsui
+    ];
+
+    nodes.machine = { config, pkgs, lib, ... }: {
+      users.extraUsers.alice = { isNormalUser = true; };
+      programs.dconf = with lib.gvariant; {
+        enable = true;
+        profiles.user.databases = [
+          {
+            settings = {
+              "test/not".locked = mkInt32 1;
+              "test/is".locked = "locked";
+            };
+            locks = [
+              "/test/is/locked"
+            ];
+          }
+        ];
+      };
+    };
+
+    testScript = ''
+      machine.succeed("test $(dconf read -d /test/not/locked) == 1")
+      machine.succeed("test $(dconf read -d /test/is/locked) == \"'locked'\"")
+      machine.fail("sudo -u alice dbus-run-session -- dconf write /test/is/locked \"@s 'unlocked'\"")
+      machine.succeed("sudo -u alice dbus-run-session -- dconf write /test/not/locked \"@i 2\"")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/deconz.nix b/nixpkgs/nixos/tests/deconz.nix
new file mode 100644
index 000000000000..cbe721ba4925
--- /dev/null
+++ b/nixpkgs/nixos/tests/deconz.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  httpPort = 800;
+in
+{
+  name = "deconz";
+
+  meta.maintainers = with lib.maintainers; [
+    bjornfor
+  ];
+
+  nodes.machine = { config, pkgs, lib, ... }: {
+    nixpkgs.config.allowUnfree = true;
+    services.deconz = {
+      enable = true;
+      inherit httpPort;
+      extraArgs = [
+        "--dbg-err=2"
+        "--dbg-info=2"
+      ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("deconz.service")
+    machine.succeed("curl -sfL http://localhost:${toString httpPort}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/deepin.nix b/nixpkgs/nixos/tests/deepin.nix
new file mode 100644
index 000000000000..d3ce79a535c1
--- /dev/null
+++ b/nixpkgs/nixos/tests/deepin.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "deepin";
+
+  meta.maintainers = lib.teams.deepin.members;
+
+  nodes.machine = { ... }: {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    virtualisation.memorySize = 2048;
+
+    services.xserver.enable = true;
+
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+
+    services.xserver.desktopManager.deepin.enable = true;
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+    in
+    ''
+      with subtest("Wait for login"):
+          machine.wait_for_x()
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if Deepin session components actually start"):
+          machine.wait_until_succeeds("pgrep -f dde-session-daemon")
+          machine.wait_for_window("dde-session-daemon")
+          machine.wait_until_succeeds("pgrep -f dde-desktop")
+          machine.wait_for_window("dde-desktop")
+
+      with subtest("Open deepin-terminal"):
+          machine.succeed("su - ${user.name} -c 'DISPLAY=:0 deepin-terminal >&2 &'")
+          machine.wait_for_window("deepin-terminal")
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/deluge.nix b/nixpkgs/nixos/tests/deluge.nix
new file mode 100644
index 000000000000..e8945fdea003
--- /dev/null
+++ b/nixpkgs/nixos/tests/deluge.nix
@@ -0,0 +1,63 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "deluge";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ flokli ];
+  };
+
+  nodes = {
+    simple = {
+      services.deluge = {
+        enable = true;
+        package = pkgs.deluge-2_x;
+        web = {
+          enable = true;
+          openFirewall = true;
+        };
+      };
+    };
+
+    declarative = {
+      services.deluge = {
+        enable = true;
+        package = pkgs.deluge-2_x;
+        openFirewall = true;
+        declarative = true;
+        config = {
+          allow_remote = true;
+          download_location = "/var/lib/deluge/my-download";
+          daemon_port = 58846;
+          listen_ports = [ 6881 6889 ];
+        };
+        web = {
+          enable = true;
+          port =  3142;
+        };
+        authFile = pkgs.writeText "deluge-auth" ''
+          localclient:a7bef72a890:10
+          andrew:password:10
+          user3:anotherpass:5
+        '';
+      };
+    };
+
+  };
+
+  testScript = ''
+    start_all()
+
+    simple.wait_for_unit("deluged")
+    simple.wait_for_unit("delugeweb")
+    simple.wait_for_open_port(8112)
+    declarative.wait_for_unit("network.target")
+    declarative.wait_until_succeeds("curl --fail http://simple:8112")
+
+    declarative.wait_for_unit("deluged")
+    declarative.wait_for_unit("delugeweb")
+    declarative.wait_until_succeeds("curl --fail http://declarative:3142")
+
+    # deluge-console always exits with 1. https://dev.deluge-torrent.org/ticket/3291
+    declarative.succeed(
+        "(deluge-console 'connect 127.0.0.1:58846 andrew password; help' || true) | grep -q 'rm.*Remove a torrent'"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/dex-oidc.nix b/nixpkgs/nixos/tests/dex-oidc.nix
new file mode 100644
index 000000000000..e54ae18ca937
--- /dev/null
+++ b/nixpkgs/nixos/tests/dex-oidc.nix
@@ -0,0 +1,78 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "dex-oidc";
+  meta.maintainers = with lib.maintainers; [ Flakebi ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ jq ];
+    services.dex = {
+      enable = true;
+      settings = {
+        issuer = "http://127.0.0.1:8080/dex";
+        storage = {
+          type = "postgres";
+          config.host = "/var/run/postgresql";
+        };
+        web.http = "127.0.0.1:8080";
+        oauth2.skipApprovalScreen = true;
+        staticClients = [
+          {
+            id = "oidcclient";
+            name = "Client";
+            redirectURIs = [ "https://example.com/callback" ];
+            secretFile = "/etc/dex/oidcclient";
+          }
+        ];
+        connectors = [
+          {
+            type = "mockPassword";
+            id = "mock";
+            name = "Example";
+            config = {
+              username = "admin";
+              password = "password";
+            };
+          }
+        ];
+      };
+    };
+
+    # This should not be set from nix but through other means to not leak the secret.
+    environment.etc."dex/oidcclient" = {
+      mode = "0400";
+      user = "dex";
+      text = "oidcclientsecret";
+    };
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases =[ "dex" ];
+      ensureUsers = [
+        {
+          name = "dex";
+          ensureDBOwnership = true;
+        }
+      ];
+    };
+  };
+
+  testScript = ''
+    with subtest("Web server gets ready"):
+        machine.wait_for_unit("dex.service")
+        # Wait until server accepts connections
+        machine.wait_until_succeeds("curl -fs 'localhost:8080/dex/auth/mock?client_id=oidcclient&response_type=code&redirect_uri=https://example.com/callback&scope=openid'")
+
+    with subtest("Login"):
+        state = machine.succeed("curl -fs 'localhost:8080/dex/auth/mock?client_id=oidcclient&response_type=code&redirect_uri=https://example.com/callback&scope=openid' | sed -n 's/.*state=\\(.*\\)\">.*/\\1/p'").strip()
+        print(f"Got state {state}")
+        machine.succeed(f"curl -fs 'localhost:8080/dex/auth/mock/login?back=&state={state}' -d 'login=admin&password=password'")
+        code = machine.succeed(f"curl -fs localhost:8080/dex/approval?req={state} | sed -n 's/.*code=\\(.*\\)&amp;.*/\\1/p'").strip()
+        print(f"Got approval code {code}")
+        bearer = machine.succeed(f"curl -fs localhost:8080/dex/token -u oidcclient:oidcclientsecret -d 'grant_type=authorization_code&redirect_uri=https://example.com/callback&code={code}' | jq .access_token -r").strip()
+        print(f"Got access token {bearer}")
+
+    with subtest("Get userinfo"):
+        assert '"sub"' in machine.succeed(
+            f"curl -fs localhost:8080/dex/userinfo --oauth2-bearer {bearer}"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/dhparams.nix b/nixpkgs/nixos/tests/dhparams.nix
new file mode 100644
index 000000000000..8d7082c11400
--- /dev/null
+++ b/nixpkgs/nixos/tests/dhparams.nix
@@ -0,0 +1,130 @@
+import ./make-test-python.nix {
+  name = "dhparams";
+
+  nodes.machine = { pkgs, ... }: {
+    security.dhparams.enable = true;
+    environment.systemPackages = [ pkgs.openssl ];
+
+    specialisation = {
+      gen1.configuration = { config, ... }: {
+        security.dhparams.params = {
+          # Use low values here because we don't want the test to run for ages.
+          foo.bits = 1024;
+          # Also use the old format to make sure the type is coerced in the right
+          # way.
+          bar = 1025;
+        };
+
+        systemd.services.foo = {
+          description = "Check systemd Ordering";
+          wantedBy = [ "multi-user.target" ];
+          before = [ "shutdown.target" ];
+          conflicts = [ "shutdown.target" ];
+          unitConfig = {
+            # This is to make sure that the dhparams generation of foo occurs
+            # before this service so we need this service to start as early as
+            # possible to provoke a race condition.
+            DefaultDependencies = false;
+
+            # We check later whether the service has been started or not.
+            ConditionPathExists = config.security.dhparams.params.foo.path;
+          };
+          serviceConfig.Type = "oneshot";
+          serviceConfig.RemainAfterExit = true;
+          # The reason we only provide an ExecStop here is to ensure that we don't
+          # accidentally trigger an error because a file system is not yet ready
+          # during very early startup (we might not even have the Nix store
+          # available, for example if future changes in NixOS use systemd mount
+          # units to do early file system initialisation).
+          serviceConfig.ExecStop = "${pkgs.coreutils}/bin/true";
+        };
+      };
+      gen2.configuration = {
+        security.dhparams.params.foo.bits = 1026;
+      };
+      gen3.configuration =  {};
+      gen4.configuration = {
+        security.dhparams.stateful = false;
+        security.dhparams.params.foo2.bits = 1027;
+        security.dhparams.params.bar2.bits = 1028;
+      };
+      gen5.configuration = {
+        security.dhparams.defaultBitSize = 1029;
+        security.dhparams.params.foo3 = {};
+        security.dhparams.params.bar3 = {};
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    getParamPath = gen: name: let
+      node = "gen${toString gen}";
+    in nodes.machine.config.specialisation.${node}.configuration.security.dhparams.params.${name}.path;
+
+    switchToGeneration = gen: let
+      switchCmd = "${nodes.machine.config.system.build.toplevel}/specialisation/gen${toString gen}/bin/switch-to-configuration test";
+    in ''
+      with machine.nested("switch to generation ${toString gen}"):
+        machine.succeed("${switchCmd}")
+    '';
+
+  in ''
+    import re
+
+
+    def assert_param_bits(path, bits):
+        with machine.nested(f"check bit size of {path}"):
+            output = machine.succeed(f"openssl dhparam -in {path} -text")
+            pattern = re.compile(r"^\s*DH Parameters:\s+\((\d+)\s+bit\)\s*$", re.M)
+            match = pattern.match(output)
+            if match is None:
+                raise Exception("bla")
+            if match[1] != str(bits):
+                raise Exception(f"bit size should be {bits} but it is {match[1]} instead.")
+
+    machine.wait_for_unit("multi-user.target")
+    ${switchToGeneration 1}
+
+    with subtest("verify startup order"):
+        machine.succeed("systemctl is-active foo.service")
+
+    with subtest("check bit sizes of dhparam files"):
+        assert_param_bits("${getParamPath 1 "foo"}", 1024)
+        assert_param_bits("${getParamPath 1 "bar"}", 1025)
+
+    ${switchToGeneration 2}
+
+    with subtest("check whether bit size has changed"):
+        assert_param_bits("${getParamPath 2 "foo"}", 1026)
+
+    with subtest("ensure that dhparams file for 'bar' was deleted"):
+        machine.fail("test -e ${getParamPath 1 "bar"}")
+
+    ${switchToGeneration 3}
+
+    with subtest("ensure that 'security.dhparams.path' has been deleted"):
+        machine.fail("test -e ${nodes.machine.config.specialisation.gen3.configuration.security.dhparams.path}")
+
+    ${switchToGeneration 4}
+
+    with subtest("check bit sizes dhparam files"):
+        assert_param_bits(
+            "${getParamPath 4 "foo2"}", 1027
+        )
+        assert_param_bits(
+            "${getParamPath 4 "bar2"}", 1028
+        )
+
+    with subtest("check whether dhparam files are in the Nix store"):
+        machine.succeed(
+            "expr match ${getParamPath 4 "foo2"} ${builtins.storeDir}",
+            "expr match ${getParamPath 4 "bar2"} ${builtins.storeDir}",
+        )
+
+    ${switchToGeneration 5}
+
+    with subtest("check whether defaultBitSize works as intended"):
+        assert_param_bits("${getParamPath 5 "foo3"}", 1029)
+        assert_param_bits("${getParamPath 5 "bar3"}", 1029)
+  '';
+}
diff --git a/nixpkgs/nixos/tests/disable-installer-tools.nix b/nixpkgs/nixos/tests/disable-installer-tools.nix
new file mode 100644
index 000000000000..69f99122753a
--- /dev/null
+++ b/nixpkgs/nixos/tests/disable-installer-tools.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
+
+{
+  name = "disable-installer-tools";
+
+  nodes.machine =
+    { pkgs, lib, ... }:
+    {
+        system.disableInstallerTools = true;
+        boot.enableContainers = false;
+        environment.defaultPackages = [];
+    };
+
+  testScript = ''
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+      with subtest("nixos installer tools should not be included"):
+          machine.fail("which nixos-rebuild")
+          machine.fail("which nixos-install")
+          machine.fail("which nixos-generate-config")
+          machine.fail("which nixos-enter")
+          machine.fail("which nixos-version")
+          machine.fail("which nixos-build-vms")
+
+      with subtest("perl should not be included"):
+          machine.fail("which perl")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/discourse.nix b/nixpkgs/nixos/tests/discourse.nix
new file mode 100644
index 000000000000..3e69a314905c
--- /dev/null
+++ b/nixpkgs/nixos/tests/discourse.nix
@@ -0,0 +1,202 @@
+# This tests Discourse by:
+#  1. logging in as the admin user
+#  2. sending a private message to the admin user through the API
+#  3. replying to that message via email.
+
+import ./make-test-python.nix (
+  { pkgs, lib, package ? pkgs.discourse, ... }:
+  let
+    certs = import ./common/acme/server/snakeoil-certs.nix;
+    clientDomain = "client.fake.domain";
+    discourseDomain = certs.domain;
+    adminPassword = "eYAX85qmMJ5GZIHLaXGDAoszD7HSZp5d";
+    secretKeyBase = "381f4ac6d8f5e49d804dae72aa9c046431d2f34c656a705c41cd52fed9b4f6f76f51549f0b55db3b8b0dded7a00d6a381ebe9a4367d2d44f5e743af6628b4d42";
+    admin = {
+      email = "alice@${clientDomain}";
+      username = "alice";
+      fullName = "Alice Admin";
+      passwordFile = "${pkgs.writeText "admin-pass" adminPassword}";
+    };
+  in
+  {
+    name = "discourse";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ talyz ];
+    };
+
+    nodes.discourse =
+      { nodes, ... }:
+      {
+        virtualisation.memorySize = 2048;
+        virtualisation.cores = 4;
+        virtualisation.useNixStoreImage = true;
+        virtualisation.writableStore = false;
+
+        imports = [ common/user-account.nix ];
+
+        security.pki.certificateFiles = [
+          certs.ca.cert
+        ];
+
+        networking.extraHosts = ''
+          127.0.0.1 ${discourseDomain}
+          ${nodes.client.networking.primaryIPAddress} ${clientDomain}
+        '';
+
+        services.postfix = {
+          enableSubmission = true;
+          enableSubmissions = true;
+          submissionsOptions = {
+            smtpd_sasl_auth_enable = "yes";
+            smtpd_client_restrictions = "permit";
+          };
+        };
+
+        environment.systemPackages = [ pkgs.jq ];
+
+        services.postgresql.package = pkgs.postgresql_13;
+
+        services.discourse = {
+          enable = true;
+          inherit admin package;
+          hostname = discourseDomain;
+          sslCertificate = "${certs.${discourseDomain}.cert}";
+          sslCertificateKey = "${certs.${discourseDomain}.key}";
+          secretKeyBaseFile = "${pkgs.writeText "secret-key-base" secretKeyBase}";
+          enableACME = false;
+          mail.outgoing.serverAddress = clientDomain;
+          mail.incoming.enable = true;
+          siteSettings = {
+            posting = {
+              min_post_length = 5;
+              min_first_post_length = 5;
+              min_personal_message_post_length = 5;
+            };
+          };
+          unicornTimeout = 900;
+        };
+
+        networking.firewall.allowedTCPPorts = [ 25 465 ];
+      };
+
+    nodes.client =
+      { nodes, ... }:
+      {
+        imports = [ common/user-account.nix ];
+
+        security.pki.certificateFiles = [
+          certs.ca.cert
+        ];
+
+        networking.extraHosts = ''
+          127.0.0.1 ${clientDomain}
+          ${nodes.discourse.networking.primaryIPAddress} ${discourseDomain}
+        '';
+
+        services.dovecot2 = {
+          enable = true;
+          protocols = [ "imap" ];
+          modules = [ pkgs.dovecot_pigeonhole ];
+        };
+
+        services.postfix = {
+          enable = true;
+          origin = clientDomain;
+          relayDomains = [ clientDomain ];
+          config = {
+            compatibility_level = "2";
+            smtpd_banner = "ESMTP server";
+            myhostname = clientDomain;
+            mydestination = clientDomain;
+          };
+        };
+
+        environment.systemPackages =
+          let
+            replyToEmail = pkgs.writeScriptBin "reply-to-email" ''
+              #!${pkgs.python3.interpreter}
+              import imaplib
+              import smtplib
+              import ssl
+              import email.header
+              from email import message_from_bytes
+              from email.message import EmailMessage
+
+              with imaplib.IMAP4('localhost') as imap:
+                  imap.login('alice', 'foobar')
+                  imap.select()
+                  status, data = imap.search(None, 'ALL')
+                  assert status == 'OK'
+
+                  nums = data[0].split()
+                  assert len(nums) == 1
+
+                  status, msg_data = imap.fetch(nums[0], '(RFC822)')
+                  assert status == 'OK'
+
+              msg = email.message_from_bytes(msg_data[0][1])
+              subject = str(email.header.make_header(email.header.decode_header(msg['Subject'])))
+              reply_to = email.header.decode_header(msg['Reply-To'])[0][0]
+              message_id = email.header.decode_header(msg['Message-ID'])[0][0]
+              date = email.header.decode_header(msg['Date'])[0][0]
+
+              ctx = ssl.create_default_context()
+              with smtplib.SMTP_SSL(host='${discourseDomain}', context=ctx) as smtp:
+                  reply = EmailMessage()
+                  reply['Subject'] = 'Re: ' + subject
+                  reply['To'] = reply_to
+                  reply['From'] = 'alice@${clientDomain}'
+                  reply['In-Reply-To'] = message_id
+                  reply['References'] = message_id
+                  reply['Date'] = date
+                  reply.set_content("Test reply.")
+
+                  smtp.send_message(reply)
+                  smtp.quit()
+            '';
+          in
+            [ replyToEmail ];
+
+        networking.firewall.allowedTCPPorts = [ 25 ];
+      };
+
+
+    testScript = { nodes }:
+      let
+        request = builtins.toJSON {
+          title = "Private message";
+          raw = "This is a test message.";
+          target_recipients = admin.username;
+          archetype = "private_message";
+        };
+      in ''
+        discourse.start()
+        client.start()
+
+        discourse.wait_for_unit("discourse.service")
+        discourse.wait_for_file("/run/discourse/sockets/unicorn.sock")
+        discourse.wait_until_succeeds("curl -sS -f https://${discourseDomain}")
+        discourse.succeed(
+            "curl -sS -f https://${discourseDomain}/session/csrf -c cookie -b cookie -H 'Accept: application/json' | jq -r '\"X-CSRF-Token: \" + .csrf' > csrf_token",
+            "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.services.discourse.admin.username}\"'",
+            "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.services.discourse.admin.username}",
+        )
+
+        client.wait_for_unit("postfix.service")
+        client.wait_for_unit("dovecot2.service")
+
+        discourse.succeed(
+            "sudo -u discourse discourse-rake api_key:create_master[master] >api_key",
+            'curl -sS -f https://${discourseDomain}/posts -X POST -H "Content-Type: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" -d \'${request}\' ',
+        )
+
+        client.wait_until_succeeds("reply-to-email")
+
+        discourse.wait_until_succeeds(
+            'curl -sS -f https://${discourseDomain}/topics/private-messages/system -H "Accept: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" | jq -e \'if .topic_list.topics[0].id != null then .topic_list.topics[0].id else null end\' >topic_id'
+        )
+        discourse.succeed(
+            'curl -sS -f https://${discourseDomain}/t/$(<topic_id) -H "Accept: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" | jq -e \'if .post_stream.posts[1].cooked == "<p>Test reply.</p>" then true else null end\' '
+        )
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/dnscrypt-proxy2.nix b/nixpkgs/nixos/tests/dnscrypt-proxy2.nix
new file mode 100644
index 000000000000..a75a745d3553
--- /dev/null
+++ b/nixpkgs/nixos/tests/dnscrypt-proxy2.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, ... }: let
+  localProxyPort = 43;
+in {
+  name = "dnscrypt-proxy2";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ joachifm ];
+  };
+
+  nodes = {
+    # A client running the recommended setup: DNSCrypt proxy as a forwarder
+    # for a caching DNS client.
+    client =
+    { ... }:
+    {
+      security.apparmor.enable = true;
+
+      services.dnscrypt-proxy2.enable = true;
+      services.dnscrypt-proxy2.settings = {
+        listen_addresses = [ "127.0.0.1:${toString localProxyPort}" ];
+        sources.public-resolvers = {
+          urls = [ "https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md" ];
+          cache_file = "public-resolvers.md";
+          minisign_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3";
+          refresh_delay = 72;
+        };
+      };
+
+      services.dnsmasq.enable = true;
+      services.dnsmasq.settings.server = [ "127.0.0.1#${toString localProxyPort}" ];
+    };
+  };
+
+  testScript = ''
+    client.wait_for_unit("dnsmasq")
+    client.wait_for_unit("dnscrypt-proxy2")
+    client.wait_until_succeeds("ss --numeric --udp --listening | grep -q ${toString localProxyPort}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/dnscrypt-wrapper/default.nix b/nixpkgs/nixos/tests/dnscrypt-wrapper/default.nix
new file mode 100644
index 000000000000..1a794931dc50
--- /dev/null
+++ b/nixpkgs/nixos/tests/dnscrypt-wrapper/default.nix
@@ -0,0 +1,148 @@
+
+{ lib, pkgs, ... }:
+
+let
+  snakeoil = import ../common/acme/server/snakeoil-certs.nix;
+
+  hosts = lib.mkForce
+   { "fd::a" = [ "server" snakeoil.domain ];
+     "fd::b" = [ "client" ];
+   };
+in
+
+{
+  name = "dnscrypt-wrapper";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  nodes = {
+    server = {
+      networking.hosts = hosts;
+      networking.interfaces.eth1.ipv6.addresses = lib.singleton
+        { address = "fd::a"; prefixLength = 64; };
+
+        services.dnscrypt-wrapper =
+          { enable = true;
+            address = "[::]";
+            port = 5353;
+            keys.expiration = 5; # days
+            keys.checkInterval = 2;  # min
+            # The keypair was generated by the command:
+            # dnscrypt-wrapper --gen-provider-keypair \
+            #  --provider-name=2.dnscrypt-cert.server \
+            providerKey.public = "${./public.key}";
+            providerKey.secret = "${./secret.key}";
+          };
+
+        # nameserver
+        services.bind.enable = true;
+        services.bind.zones = lib.singleton
+          { name = ".";
+            master = true;
+            file = pkgs.writeText "root.zone" ''
+              $TTL 3600
+              . IN SOA example.org. admin.example.org. ( 1 3h 1h 1w 1d )
+              . IN NS example.org.
+              example.org. IN AAAA 2001:db8::1
+            '';
+          };
+
+        # webserver
+        services.nginx.enable = true;
+        services.nginx.virtualHosts.${snakeoil.domain} =
+          { onlySSL = true;
+            listenAddresses = [ "localhost" ];
+            sslCertificate = snakeoil.${snakeoil.domain}.cert;
+            sslCertificateKey = snakeoil.${snakeoil.domain}.key;
+            locations."/ip".extraConfig = ''
+              default_type text/plain;
+              return 200 "Ciao $remote_addr!\n";
+            '';
+          };
+
+        # demultiplex HTTP and DNS from port 443
+        services.sslh =
+          { enable = true;
+            method = "ev";
+            settings.transparent = true;
+            settings.listen = lib.mkForce
+              [ { host = "server"; port = "443"; is_udp = false; }
+                { host = "server"; port = "443"; is_udp = true; }
+              ];
+            settings.protocols =
+              [ # Send TLS to webserver (TCP)
+                { name = "tls"; host= "localhost"; port= "443"; }
+                # Send DNSCrypt to dnscrypt-wrapper (TCP or UDP)
+                { name = "anyprot"; host = "localhost"; port = "5353"; }
+                { name = "anyprot"; host = "localhost"; port = "5353"; is_udp = true;}
+              ];
+          };
+
+        networking.firewall.allowedTCPPorts = [ 443 ];
+        networking.firewall.allowedUDPPorts = [ 443 ];
+      };
+
+    client = {
+      networking.hosts = hosts;
+      networking.interfaces.eth1.ipv6.addresses = lib.singleton
+        { address = "fd::b"; prefixLength = 64; };
+
+      services.dnscrypt-proxy2.enable = true;
+      services.dnscrypt-proxy2.upstreamDefaults = false;
+      services.dnscrypt-proxy2.settings =
+        { server_names = [ "server" ];
+          listen_addresses = [ "[::1]:53" ];
+          cache = false;
+          # Computed using https://dnscrypt.info/stamps/
+          static.server.stamp =
+            "sdns://AQAAAAAAAAAADzE5Mi4xNjguMS4yOjQ0MyAUQdg6"
+            +"_RIIpK6pHkINhrv7nxwIG5c7b_m5NJVT3A1AXRYyLmRuc2NyeXB0LWNlcnQuc2VydmVy";
+        };
+      networking.nameservers = [ "::1" ];
+      security.pki.certificateFiles = [ snakeoil.ca.cert ];
+    };
+
+  };
+
+  testScript = ''
+    with subtest("The server can generate the ephemeral keypair"):
+        server.wait_for_unit("dnscrypt-wrapper")
+        server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.key")
+        server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.crt")
+        almost_expiration = server.succeed("date --date '4days 23 hours 56min'").strip()
+
+    with subtest("The DNSCrypt client can connect to the server"):
+        server.wait_for_unit("sslh")
+        client.wait_until_succeeds("journalctl -u dnscrypt-proxy2 --grep '\[server\] OK'")
+
+    with subtest("HTTP client can connect to the server"):
+        server.wait_for_unit("nginx")
+        client.succeed("curl -s --fail https://${snakeoil.domain}/ip | grep -q fd::b")
+
+    with subtest("DNS queries over UDP are working"):
+        server.wait_for_unit("bind")
+        client.wait_for_open_port(53)
+        assert "2001:db8::1" in client.wait_until_succeeds(
+            "host -U example.org"
+        ), "The IP address of 'example.org' does not match 2001:db8::1"
+
+    with subtest("DNS queries over TCP are working"):
+        server.wait_for_unit("bind")
+        client.wait_for_open_port(53)
+        assert "2001:db8::1" in client.wait_until_succeeds(
+            "host -T example.org"
+        ), "The IP address of 'example.org' does not match 2001:db8::1"
+
+    with subtest("The server rotates the ephemeral keys"):
+        # advance time by a little less than 5 days
+        server.succeed(f"date -s '{almost_expiration}'")
+        client.succeed(f"date -s '{almost_expiration}'")
+        server.wait_for_file("/var/lib/dnscrypt-wrapper/oldkeys")
+
+    with subtest("The client can still connect to the server"):
+        client.systemctl("restart dnscrypt-proxy2")
+        client.wait_until_succeeds("host -T example.org")
+        client.wait_until_succeeds("host -U example.org")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/dnscrypt-wrapper/public.key b/nixpkgs/nixos/tests/dnscrypt-wrapper/public.key
new file mode 100644
index 000000000000..80232b97f529
--- /dev/null
+++ b/nixpkgs/nixos/tests/dnscrypt-wrapper/public.key
@@ -0,0 +1 @@
+AØ:ý¤®©B
†»ûŸ—;où¹4•SÜ
@]
\ No newline at end of file
diff --git a/nixpkgs/nixos/tests/dnscrypt-wrapper/secret.key b/nixpkgs/nixos/tests/dnscrypt-wrapper/secret.key
new file mode 100644
index 000000000000..01fbf8e08b7a
--- /dev/null
+++ b/nixpkgs/nixos/tests/dnscrypt-wrapper/secret.key
@@ -0,0 +1 @@
+G½>Æ©» ì>Ðà¥(Ò²‡¼J•«º=Ÿ„ÝÁlìAØ:ý¤®©B
†»ûŸ—;où¹4•SÜ
@]
\ No newline at end of file
diff --git a/nixpkgs/nixos/tests/dnsdist.nix b/nixpkgs/nixos/tests/dnsdist.nix
new file mode 100644
index 000000000000..9921be419a75
--- /dev/null
+++ b/nixpkgs/nixos/tests/dnsdist.nix
@@ -0,0 +1,113 @@
+{ pkgs, runTest }:
+
+let
+
+  inherit (pkgs) lib;
+
+  baseConfig = {
+    networking.nameservers = [ "::1" ];
+    services.bind = {
+      enable = true;
+      extraOptions = "empty-zones-enable no;";
+      zones = lib.singleton {
+        name = ".";
+        master = true;
+        file = pkgs.writeText "root.zone" ''
+          $TTL 3600
+          . IN SOA ns.example.org. admin.example.org. ( 1 3h 1h 1w 1d )
+          . IN NS ns.example.org.
+
+          ns.example.org. IN A    192.168.0.1
+          ns.example.org. IN AAAA abcd::1
+
+          1.0.168.192.in-addr.arpa IN PTR ns.example.org.
+        '';
+      };
+    };
+    services.dnsdist = {
+      enable = true;
+      listenPort = 5353;
+      extraConfig = ''
+        newServer({address="127.0.0.1:53", name="local-bind"})
+      '';
+    };
+  };
+
+in
+
+{
+
+  base = runTest {
+    name = "dnsdist-base";
+    meta.maintainers = with lib.maintainers; [ jojosch ];
+
+    nodes.machine = baseConfig;
+
+    testScript = ''
+      machine.wait_for_unit("bind.service")
+      machine.wait_for_open_port(53)
+      machine.succeed("host -p 53 192.168.0.1 | grep -qF ns.example.org")
+
+      machine.wait_for_unit("dnsdist.service")
+      machine.wait_for_open_port(5353)
+      machine.succeed("host -p 5353 192.168.0.1 | grep -qF ns.example.org")
+    '';
+  };
+
+  dnscrypt = runTest {
+    name = "dnsdist-dnscrypt";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+    nodes.server = lib.mkMerge [
+      baseConfig
+      {
+        networking.firewall.allowedTCPPorts = [ 443 ];
+        networking.firewall.allowedUDPPorts = [ 443 ];
+        services.dnsdist.dnscrypt.enable = true;
+        services.dnsdist.dnscrypt.providerKey = "${./dnscrypt-wrapper/secret.key}";
+      }
+    ];
+
+    nodes.client = {
+      services.dnscrypt-proxy2.enable = true;
+      services.dnscrypt-proxy2.upstreamDefaults = false;
+      services.dnscrypt-proxy2.settings =
+        { server_names = [ "server" ];
+          listen_addresses = [ "[::1]:53" ];
+          cache = false;
+          # Computed using https://dnscrypt.info/stamps/
+          static.server.stamp =
+            "sdns://AQAAAAAAAAAADzE5Mi4xNjguMS4yOjQ0MyAUQdg6_RIIpK6pHkINhrv7nxwIG5c7b_m5NJVT3A1AXRYyLmRuc2NyeXB0LWNlcnQuc2VydmVy";
+        };
+      networking.nameservers = [ "::1" ];
+    };
+
+    testScript = ''
+      with subtest("The DNSCrypt server is accepting connections"):
+          server.wait_for_unit("bind.service")
+          server.wait_for_unit("dnsdist.service")
+          server.wait_for_open_port(443)
+          almost_expiration = server.succeed("date --date '14min'").strip()
+
+      with subtest("The DNSCrypt client can connect to the server"):
+          client.wait_until_succeeds("journalctl -u dnscrypt-proxy2 --grep '\[server\] OK'")
+
+      with subtest("DNS queries over UDP are working"):
+          client.wait_for_open_port(53)
+          client.succeed("host -U 192.168.0.1 | grep -qF ns.example.org")
+
+      with subtest("DNS queries over TCP are working"):
+          client.wait_for_open_port(53)
+          client.succeed("host -T 192.168.0.1 | grep -qF ns.example.org")
+
+      with subtest("The server rotates the ephemeral keys"):
+          server.succeed(f"date -s '{almost_expiration}'")
+          client.succeed(f"date -s '{almost_expiration}'")
+          server.wait_until_succeeds("journalctl -u dnsdist --grep 'rotated certificate'")
+
+      with subtest("The client can still connect to the server"):
+          client.wait_until_succeeds("host -T 192.168.0.1")
+          client.wait_until_succeeds("host -U 192.168.0.1")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/doas.nix b/nixpkgs/nixos/tests/doas.nix
new file mode 100644
index 000000000000..2aa8b02caf57
--- /dev/null
+++ b/nixpkgs/nixos/tests/doas.nix
@@ -0,0 +1,96 @@
+# Some tests to ensure doas is working properly.
+import ./make-test-python.nix (
+  { lib, ... }: {
+    name = "doas";
+    meta.maintainers = with lib.maintainers; [ cole-h ];
+
+    nodes.machine =
+      { ... }:
+        {
+          users.groups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
+          users.users = {
+            test0 = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+            test1 = { isNormalUser = true; };
+            test2 = { isNormalUser = true; extraGroups = [ "foobar" ]; };
+            test3 = { isNormalUser = true; extraGroups = [ "barfoo" ]; };
+            test4 = { isNormalUser = true; extraGroups = [ "baz" ]; };
+            test5 = { isNormalUser = true; };
+            test6 = { isNormalUser = true; };
+            test7 = { isNormalUser = true; };
+          };
+
+          security.doas = {
+            enable = true;
+            wheelNeedsPassword = false;
+
+            extraRules = [
+              { users = [ "test1" ]; groups = [ "foobar" ]; }
+              { users = [ "test2" ]; noPass = true; setEnv = [ "CORRECT" "HORSE=BATTERY" ]; }
+              { groups = [ "barfoo" 1337 ]; noPass = true; }
+              { users = [ "test5" ]; noPass = true; keepEnv = true; runAs = "test1"; }
+              { users = [ "test6" ]; noPass = true; keepEnv = true; setEnv = [ "-STAPLE" ]; }
+              { users = [ "test7" ]; noPass = true; setEnv = [ "-SSH_AUTH_SOCK" ]; }
+            ];
+          };
+        };
+
+    testScript = ''
+      with subtest("users in wheel group should have passwordless doas"):
+          machine.succeed('su - test0 -c "doas -u root true"')
+
+      with subtest("test1 user should not be able to use doas without password"):
+          machine.fail('su - test1 -c "doas -n -u root true"')
+
+      with subtest("test2 user should be able to keep some env"):
+          if "CORRECT=1" not in machine.succeed('su - test2 -c "CORRECT=1 doas env"'):
+              raise Exception("failed to keep CORRECT")
+
+          if "HORSE=BATTERY" not in machine.succeed('su - test2 -c "doas env"'):
+              raise Exception("failed to setenv HORSE=BATTERY")
+
+      with subtest("users in group 'barfoo' shouldn't require password"):
+          machine.succeed("doas -u test3 doas -n -u root true")
+
+      with subtest("users in group 'baz' (GID 1337) shouldn't require password"):
+          machine.succeed("doas -u test4 doas -n -u root echo true")
+
+      with subtest("test5 user should be able to run commands under test1"):
+          machine.succeed("doas -u test5 doas -n -u test1 true")
+
+      with subtest("test5 user should not be able to run commands under root"):
+          machine.fail("doas -u test5 doas -n -u root true")
+
+      with subtest("test6 user should be able to keepenv"):
+          envs = ["BATTERY=HORSE", "CORRECT=false"]
+          out = machine.succeed(
+              'su - test6 -c "BATTERY=HORSE CORRECT=false STAPLE=Tr0ub4dor doas env"'
+          )
+
+          if not all(env in out for env in envs):
+              raise Exception("failed to keep BATTERY or CORRECT")
+          if "STAPLE=Tr0ub4dor" in out:
+              raise Exception("failed to exclude STAPLE")
+
+      with subtest("test7 should not have access to SSH_AUTH_SOCK"):
+          if "SSH_AUTH_SOCK=HOLEY" in machine.succeed(
+              'su - test7 -c "SSH_AUTH_SOCK=HOLEY doas env"'
+          ):
+              raise Exception("failed to exclude SSH_AUTH_SOCK")
+
+      # Test that the doas setuid wrapper precedes the unwrapped version in PATH after
+      # calling doas.
+      # The PATH set by doas is defined in
+      # ../../pkgs/tools/security/doas/0001-add-NixOS-specific-dirs-to-safe-PATH.patch
+      with subtest("recursive calls to doas from subprocesses should succeed"):
+          machine.succeed('doas -u test0 sh -c "doas -u test0 true"')
+
+      with subtest("test0 should inherit TERMINFO_DIRS from the user environment"):
+          dirs = machine.succeed(
+               "su - test0 -c 'doas -u root $SHELL -c \"echo \$TERMINFO_DIRS\"'"
+          )
+
+          if not "test0" in dirs:
+             raise Exception(f"user profile TERMINFO_DIRS is not preserved: {dirs}")
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/docker-registry.nix b/nixpkgs/nixos/tests/docker-registry.nix
new file mode 100644
index 000000000000..db20cb52c3e3
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-registry.nix
@@ -0,0 +1,61 @@
+# This test runs docker-registry and check if it works
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "docker-registry";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ globin ironpinguin ];
+  };
+
+  nodes = {
+    registry = { ... }: {
+      services.dockerRegistry.enable = true;
+      services.dockerRegistry.enableDelete = true;
+      services.dockerRegistry.port = 8080;
+      services.dockerRegistry.listenAddress = "0.0.0.0";
+      services.dockerRegistry.enableGarbageCollect = true;
+      networking.firewall.allowedTCPPorts = [ 8080 ];
+    };
+
+    client1 = { ... }: {
+      virtualisation.docker.enable = true;
+      virtualisation.docker.extraOptions = "--insecure-registry registry:8080";
+    };
+
+    client2 = { ... }: {
+      virtualisation.docker.enable = true;
+      virtualisation.docker.extraOptions = "--insecure-registry registry:8080";
+    };
+  };
+
+  testScript = ''
+    client1.start()
+    client1.wait_for_unit("docker.service")
+    client1.succeed("tar cv --files-from /dev/null | docker import - scratch")
+    client1.succeed("docker tag scratch registry:8080/scratch")
+
+    registry.start()
+    registry.wait_for_unit("docker-registry.service")
+    registry.wait_for_open_port(8080)
+    client1.succeed("docker push registry:8080/scratch")
+
+    client2.start()
+    client2.wait_for_unit("docker.service")
+    client2.succeed("docker pull registry:8080/scratch")
+    client2.succeed("docker images | grep scratch")
+
+    client2.succeed(
+        "curl -fsS -X DELETE registry:8080/v2/scratch/manifests/$(curl -fsS -I -H\"Accept: application/vnd.docker.distribution.manifest.v2+json\" registry:8080/v2/scratch/manifests/latest | grep Docker-Content-Digest | sed -e 's/Docker-Content-Digest: //' | tr -d '\\r')"
+    )
+
+    registry.systemctl("start docker-registry-garbage-collect.service")
+    registry.wait_until_fails("systemctl status docker-registry-garbage-collect.service")
+    registry.wait_for_unit("docker-registry.service")
+
+    registry.fail("ls -l /var/lib/docker-registry/docker/registry/v2/blobs/sha256/*/*/data")
+
+    client1.succeed("docker push registry:8080/scratch")
+    registry.succeed(
+        "ls -l /var/lib/docker-registry/docker/registry/v2/blobs/sha256/*/*/data"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/docker-rootless.nix b/nixpkgs/nixos/tests/docker-rootless.nix
new file mode 100644
index 000000000000..e2a926eb3cb0
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-rootless.nix
@@ -0,0 +1,41 @@
+# This test runs docker and checks if simple container starts
+
+import ./make-test-python.nix ({ lib, pkgs, ...} : {
+  name = "docker-rootless";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ abbradar ];
+  };
+
+  nodes = {
+    machine = { pkgs, ... }: {
+      virtualisation.docker.rootless.enable = true;
+
+      users.users.alice = {
+        uid = 1000;
+        isNormalUser = true;
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.config.users.users.alice;
+      sudo = lib.concatStringsSep " " [
+        "XDG_RUNTIME_DIR=/run/user/${toString user.uid}"
+        "DOCKER_HOST=unix:///run/user/${toString user.uid}/docker.sock"
+        "sudo" "--preserve-env=XDG_RUNTIME_DIR,DOCKER_HOST" "-u" "alice"
+      ];
+    in ''
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("loginctl enable-linger alice")
+      machine.wait_until_succeeds("${sudo} systemctl --user is-active docker.service")
+
+      machine.succeed("tar cv --files-from /dev/null | ${sudo} docker import - scratchimg")
+      machine.succeed(
+          "${sudo} docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+      )
+      machine.succeed("${sudo} docker ps | grep sleeping")
+      machine.succeed("${sudo} docker stop sleeping")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/docker-tools-cross.nix b/nixpkgs/nixos/tests/docker-tools-cross.nix
new file mode 100644
index 000000000000..14cb14ceeaea
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-tools-cross.nix
@@ -0,0 +1,80 @@
+# Not everyone has a suitable remote builder set up, so the cross-compilation
+# tests that _include_ running the result are separate. That way, most people
+# can run the majority of the test suite without the extra setup.
+
+
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+
+  remoteSystem =
+    if pkgs.stdenv.hostPlatform.system == "aarch64-linux"
+    then "x86_64-linux"
+    else "aarch64-linux";
+
+  remoteCrossPkgs = import ../.. /*nixpkgs*/ {
+    # NOTE: This is the machine that runs the build -  local from the
+    #       'perspective' of the build script.
+    localSystem = remoteSystem;
+
+    # NOTE: Since this file can't control where the test will be _run_ we don't
+    #       cross-compile _to_ a different system but _from_ a different system
+    crossSystem = pkgs.stdenv.hostPlatform.system;
+  };
+
+  hello1 = remoteCrossPkgs.dockerTools.buildImage {
+    name = "hello1";
+    tag = "latest";
+    copyToRoot = remoteCrossPkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ remoteCrossPkgs.hello ];
+    };
+  };
+
+  hello2 = remoteCrossPkgs.dockerTools.buildLayeredImage {
+    name = "hello2";
+    tag = "latest";
+    contents = remoteCrossPkgs.hello;
+  };
+
+in {
+  name = "docker-tools";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ roberth ];
+  };
+
+  nodes = {
+    docker = { ... }: {
+      virtualisation = {
+        diskSize = 2048;
+        docker.enable = true;
+      };
+    };
+  };
+
+  testScript = ''
+    docker.wait_for_unit("sockets.target")
+
+    with subtest("Ensure cross compiled buildImage image can run."):
+        docker.succeed(
+            "docker load --input='${hello1}'"
+        )
+        assert "Hello, world!" in docker.succeed(
+            "docker run --rm ${hello1.imageName} hello",
+        )
+        docker.succeed(
+            "docker rmi ${hello1.imageName}",
+        )
+
+    with subtest("Ensure cross compiled buildLayeredImage image can run."):
+        docker.succeed(
+            "docker load --input='${hello2}'"
+        )
+        assert "Hello, world!" in docker.succeed(
+            "docker run --rm ${hello2.imageName} hello",
+        )
+        docker.succeed(
+            "docker rmi ${hello2.imageName}",
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/docker-tools-overlay.nix b/nixpkgs/nixos/tests/docker-tools-overlay.nix
new file mode 100644
index 000000000000..6781388e639b
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-tools-overlay.nix
@@ -0,0 +1,33 @@
+# this test creates a simple GNU image with docker tools and sees if it executes
+
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "docker-tools-overlay";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lnl7 roberth ];
+  };
+
+  nodes = {
+    docker =
+      { ... }:
+      {
+        virtualisation.docker.enable = true;
+        virtualisation.docker.storageDriver = "overlay";  # defaults to overlay2
+      };
+  };
+
+  testScript = ''
+      docker.wait_for_unit("sockets.target")
+
+      docker.succeed(
+          "docker load --input='${pkgs.dockerTools.examples.bash}'",
+          "docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version",
+      )
+
+      # Check if the nix store has correct user permissions depending on what
+      # storage driver is used, incorrectly built images can show up as readonly.
+      # drw-------  3 0 0   3 Apr 14 11:36 /nix
+      # drw------- 99 0 0 100 Apr 14 11:36 /nix/store
+      docker.succeed("docker run --rm -u 1000:1000 ${pkgs.dockerTools.examples.bash.imageName} bash --version")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/docker-tools.nix b/nixpkgs/nixos/tests/docker-tools.nix
new file mode 100644
index 000000000000..f252eb9ff61e
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker-tools.nix
@@ -0,0 +1,608 @@
+# this test creates a simple GNU image with docker tools and sees if it executes
+
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  # nixpkgs#214434: dockerTools.buildImage fails to unpack base images
+  # containing duplicate layers when those duplicate tarballs
+  # appear under the manifest's 'Layers'. Docker can generate images
+  # like this even though dockerTools does not.
+  repeatedLayerTestImage =
+    let
+      # Rootfs diffs for layers 1 and 2 are identical (and empty)
+      layer1 = pkgs.dockerTools.buildImage {  name = "empty";  };
+      layer2 = layer1.overrideAttrs (_: { fromImage = layer1; });
+      repeatedRootfsDiffs = pkgs.runCommand "image-with-links.tar" {
+        nativeBuildInputs = [pkgs.jq];
+      } ''
+        mkdir contents
+        tar -xf "${layer2}" -C contents
+        cd contents
+        first_rootfs=$(jq -r '.[0].Layers[0]' manifest.json)
+        second_rootfs=$(jq -r '.[0].Layers[1]' manifest.json)
+        target_rootfs=$(sha256sum "$first_rootfs" | cut -d' ' -f 1).tar
+
+        # Replace duplicated rootfs diffs with symlinks to one tarball
+        chmod -R ug+w .
+        mv "$first_rootfs" "$target_rootfs"
+        rm "$second_rootfs"
+        ln -s "../$target_rootfs" "$first_rootfs"
+        ln -s "../$target_rootfs" "$second_rootfs"
+
+        # Update manifest's layers to use the symlinks' target
+        cat manifest.json | \
+        jq ".[0].Layers[0] = \"$target_rootfs\"" |
+        jq ".[0].Layers[1] = \"$target_rootfs\"" > manifest.json.new
+        mv manifest.json.new manifest.json
+
+        tar --sort=name --hard-dereference -cf $out .
+        '';
+    in pkgs.dockerTools.buildImage {
+      fromImage = repeatedRootfsDiffs;
+      name = "repeated-layer-test";
+      tag = "latest";
+      copyToRoot = pkgs.bash;
+      # A runAsRoot script is required to force previous layers to be unpacked
+      runAsRoot = ''
+        echo 'runAsRoot has run.'
+      '';
+    };
+
+  chownTestImage =
+    pkgs.dockerTools.streamLayeredImage {
+      name = "chown-test";
+      tag = "latest";
+      enableFakechroot = true;
+      fakeRootCommands = ''
+        touch /testfile
+        chown 12345:12345 /testfile
+      '';
+      config.Cmd = [ "${pkgs.coreutils}/bin/stat" "-c" "%u:%g" "/testfile" ];
+    };
+in {
+  name = "docker-tools";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lnl7 roberth ];
+  };
+
+  nodes = {
+    docker = { ... }: {
+      virtualisation = {
+        diskSize = 3072;
+        docker.enable = true;
+      };
+    };
+  };
+
+  testScript = with pkgs.dockerTools; ''
+    unix_time_second1 = "1970-01-01T00:00:01Z"
+
+    docker.wait_for_unit("sockets.target")
+
+    with subtest("includeStorePath"):
+        with subtest("assumption"):
+            docker.succeed("${examples.helloOnRoot} | docker load")
+            docker.succeed("docker run --rm hello | grep -i hello")
+            docker.succeed("docker image rm hello:latest")
+
+        with subtest("includeStorePath = false; breaks example"):
+            docker.succeed("${examples.helloOnRootNoStore} | docker load")
+            docker.fail("docker run --rm hello | grep -i hello")
+            docker.succeed("docker image rm hello:latest")
+        with subtest("includeStorePath = false; breaks example (fakechroot)"):
+            docker.succeed("${examples.helloOnRootNoStoreFakechroot} | docker load")
+            docker.fail("docker run --rm hello | grep -i hello")
+            docker.succeed("docker image rm hello:latest")
+
+        with subtest("Ensure ZERO paths are added to the store"):
+            docker.fail("${examples.helloOnRootNoStore} | ${pkgs.crane}/bin/crane export - - | tar t | grep 'nix/store/'")
+        with subtest("Ensure ZERO paths are added to the store (fakechroot)"):
+            docker.fail("${examples.helloOnRootNoStoreFakechroot} | ${pkgs.crane}/bin/crane export - - | tar t | grep 'nix/store/'")
+
+        with subtest("includeStorePath = false; works with mounted store"):
+            docker.succeed("${examples.helloOnRootNoStore} | docker load")
+            docker.succeed("docker run --rm --volume ${builtins.storeDir}:${builtins.storeDir}:ro hello | grep -i hello")
+            docker.succeed("docker image rm hello:latest")
+        with subtest("includeStorePath = false; works with mounted store (fakechroot)"):
+            docker.succeed("${examples.helloOnRootNoStoreFakechroot} | docker load")
+            docker.succeed("docker run --rm --volume ${builtins.storeDir}:${builtins.storeDir}:ro hello | grep -i hello")
+            docker.succeed("docker image rm hello:latest")
+
+    with subtest("Ensure Docker images use a stable date by default"):
+        docker.succeed(
+            "docker load --input='${examples.bash}'"
+        )
+        assert unix_time_second1 in docker.succeed(
+            "docker inspect ${examples.bash.imageName} "
+            + "| ${pkgs.jq}/bin/jq -r .[].Created",
+        )
+
+    docker.succeed("docker run --rm ${examples.bash.imageName} bash --version")
+    # Check imageTag attribute matches image
+    docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.bash.imageTag}'")
+    docker.succeed("docker rmi ${examples.bash.imageName}")
+
+    # The remaining combinations
+    with subtest("Ensure imageTag attribute matches image"):
+        docker.succeed(
+            "docker load --input='${examples.bashNoTag}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTag.imageTag}'"
+        )
+        docker.succeed("docker rmi ${examples.bashNoTag.imageName}:${examples.bashNoTag.imageTag}")
+
+        docker.succeed(
+            "docker load --input='${examples.bashNoTagLayered}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTagLayered.imageTag}'"
+        )
+        docker.succeed("docker rmi ${examples.bashNoTagLayered.imageName}:${examples.bashNoTagLayered.imageTag}")
+
+        docker.succeed(
+            "${examples.bashNoTagStreamLayered} | docker load"
+        )
+        docker.succeed(
+            "docker images --format '{{.Tag}}' | grep -F '${examples.bashNoTagStreamLayered.imageTag}'"
+        )
+        docker.succeed(
+            "docker rmi ${examples.bashNoTagStreamLayered.imageName}:${examples.bashNoTagStreamLayered.imageTag}"
+        )
+
+        docker.succeed(
+            "docker load --input='${examples.nixLayered}'"
+        )
+        docker.succeed("docker images --format '{{.Tag}}' | grep -F '${examples.nixLayered.imageTag}'")
+        docker.succeed("docker rmi ${examples.nixLayered.imageName}")
+
+    with subtest("Check that images with alternative compression schemas load"):
+        docker.succeed(
+            "docker load --input='${examples.bashZstdCompressed}'",
+            "docker rmi ${examples.bashZstdCompressed.imageName}",
+        )
+        docker.succeed(
+            "docker load --input='${examples.bashUncompressed}'",
+            "docker rmi ${examples.bashUncompressed.imageName}",
+        )
+
+    with subtest(
+        "Check if the nix store is correctly initialized by listing "
+        "dependencies of the installed Nix binary"
+    ):
+        docker.succeed(
+            "docker load --input='${examples.nix}'",
+            "docker run --rm ${examples.nix.imageName} nix-store -qR ${pkgs.nix}",
+            "docker rmi ${examples.nix.imageName}",
+        )
+
+    with subtest(
+        "Ensure (layered) nix store has correct permissions "
+        "and that the container starts when its process does not have uid 0"
+    ):
+        docker.succeed(
+            "docker load --input='${examples.bashLayeredWithUser}'",
+            "docker run -u somebody --rm ${examples.bashLayeredWithUser.imageName} ${pkgs.bash}/bin/bash -c 'test 555 == $(stat --format=%a /nix) && test 555 == $(stat --format=%a /nix/store)'",
+            "docker rmi ${examples.bashLayeredWithUser.imageName}",
+        )
+
+    with subtest("The nix binary symlinks are intact"):
+        docker.succeed(
+            "docker load --input='${examples.nix}'",
+            "docker run --rm ${examples.nix.imageName} ${pkgs.bash}/bin/bash -c 'test nix == $(readlink ${pkgs.nix}/bin/nix-daemon)'",
+            "docker rmi ${examples.nix.imageName}",
+        )
+
+    with subtest("The nix binary symlinks are intact when the image is layered"):
+        docker.succeed(
+            "docker load --input='${examples.nixLayered}'",
+            "docker run --rm ${examples.nixLayered.imageName} ${pkgs.bash}/bin/bash -c 'test nix == $(readlink ${pkgs.nix}/bin/nix-daemon)'",
+            "docker rmi ${examples.nixLayered.imageName}",
+        )
+
+    with subtest("The pullImage tool works"):
+        docker.succeed(
+            "docker load --input='${examples.testNixFromDockerHub}'",
+            "docker run --rm nix:2.2.1 nix-store --version",
+            "docker rmi nix:2.2.1",
+        )
+
+    with subtest("runAsRoot and entry point work"):
+        docker.succeed(
+            "docker load --input='${examples.nginx}'",
+            "docker run --name nginx -d -p 8000:80 ${examples.nginx.imageName}",
+        )
+        docker.wait_until_succeeds("curl -f http://localhost:8000/")
+        docker.succeed(
+            "docker rm --force nginx",
+            "docker rmi '${examples.nginx.imageName}'",
+        )
+
+    with subtest("A pulled image can be used as base image"):
+        docker.succeed(
+            "docker load --input='${examples.onTopOfPulledImage}'",
+            "docker run --rm ontopofpulledimage hello",
+            "docker rmi ontopofpulledimage",
+        )
+
+    with subtest("Regression test for issue #34779"):
+        docker.succeed(
+            "docker load --input='${examples.runAsRootExtraCommands}'",
+            "docker run --rm runasrootextracommands cat extraCommands",
+            "docker run --rm runasrootextracommands cat runAsRoot",
+            "docker rmi '${examples.runAsRootExtraCommands.imageName}'",
+        )
+
+    with subtest("Ensure Docker images can use an unstable date"):
+        docker.succeed(
+            "docker load --input='${examples.unstableDate}'"
+        )
+        assert unix_time_second1 not in docker.succeed(
+            "docker inspect ${examples.unstableDate.imageName} "
+            + "| ${pkgs.jq}/bin/jq -r .[].Created"
+        )
+
+    with subtest("Ensure Layered Docker images can use an unstable date"):
+        docker.succeed(
+            "docker load --input='${examples.unstableDateLayered}'"
+        )
+        assert unix_time_second1 not in docker.succeed(
+            "docker inspect ${examples.unstableDateLayered.imageName} "
+            + "| ${pkgs.jq}/bin/jq -r .[].Created"
+        )
+
+    with subtest("Ensure Layered Docker images work"):
+        docker.succeed(
+            "docker load --input='${examples.layered-image}'",
+            "docker run --rm ${examples.layered-image.imageName}",
+            "docker run --rm ${examples.layered-image.imageName} cat extraCommands",
+        )
+
+    with subtest("Ensure images built on top of layered Docker images work"):
+        docker.succeed(
+            "docker load --input='${examples.layered-on-top}'",
+            "docker run --rm ${examples.layered-on-top.imageName}",
+        )
+
+    with subtest("Ensure layered images built on top of layered Docker images work"):
+        docker.succeed(
+            "docker load --input='${examples.layered-on-top-layered}'",
+            "docker run --rm ${examples.layered-on-top-layered.imageName}",
+        )
+
+
+    def set_of_layers(image_name):
+        return set(
+            docker.succeed(
+                f"docker inspect {image_name} "
+                + "| ${pkgs.jq}/bin/jq -r '.[] | .RootFS.Layers | .[]'"
+            ).split()
+        )
+
+
+    with subtest("Ensure layers are shared between images"):
+        docker.succeed(
+            "docker load --input='${examples.another-layered-image}'"
+        )
+        layers1 = set_of_layers("${examples.layered-image.imageName}")
+        layers2 = set_of_layers("${examples.another-layered-image.imageName}")
+        assert bool(layers1 & layers2)
+
+    with subtest("Ensure order of layers is correct"):
+        docker.succeed(
+            "docker load --input='${examples.layersOrder}'"
+        )
+
+        for index in 1, 2, 3:
+            assert f"layer{index}" in docker.succeed(
+                f"docker run --rm  ${examples.layersOrder.imageName} cat /tmp/layer{index}"
+            )
+
+    with subtest("Ensure layers unpacked in correct order before runAsRoot runs"):
+        assert "abc" in docker.succeed(
+            "docker load --input='${examples.layersUnpackOrder}'",
+            "docker run --rm ${examples.layersUnpackOrder.imageName} cat /layer-order"
+        )
+
+    with subtest("Ensure repeated base layers handled by buildImage"):
+        docker.succeed(
+            "docker load --input='${repeatedLayerTestImage}'",
+            "docker run --rm ${repeatedLayerTestImage.imageName} /bin/bash -c 'exit 0'"
+        )
+
+    with subtest("Ensure environment variables are correctly inherited"):
+        docker.succeed(
+            "docker load --input='${examples.environmentVariables}'"
+        )
+        out = docker.succeed("docker run --rm ${examples.environmentVariables.imageName} env")
+        env = out.splitlines()
+        assert "FROM_PARENT=true" in env, "envvars from the parent should be preserved"
+        assert "FROM_CHILD=true" in env, "envvars from the child should be preserved"
+        assert "LAST_LAYER=child" in env, "envvars from the child should take priority"
+
+    with subtest("Ensure environment variables of layered images are correctly inherited"):
+        docker.succeed(
+            "docker load --input='${examples.environmentVariablesLayered}'"
+        )
+        out = docker.succeed("docker run --rm ${examples.environmentVariablesLayered.imageName} env")
+        env = out.splitlines()
+        assert "FROM_PARENT=true" in env, "envvars from the parent should be preserved"
+        assert "FROM_CHILD=true" in env, "envvars from the child should be preserved"
+        assert "LAST_LAYER=child" in env, "envvars from the child should take priority"
+
+    with subtest(
+        "Ensure inherited environment variables of layered images are correctly resolved"
+    ):
+        # Read environment variables as stored in image config
+        config = docker.succeed(
+            "tar -xOf ${examples.environmentVariablesLayered} manifest.json | ${pkgs.jq}/bin/jq -r .[].Config"
+        ).strip()
+        out = docker.succeed(
+            f"tar -xOf ${examples.environmentVariablesLayered} {config} | ${pkgs.jq}/bin/jq -r '.config.Env | .[]'"
+        )
+        env = out.splitlines()
+        assert (
+            sum(entry.startswith("LAST_LAYER") for entry in env) == 1
+        ), "envvars overridden by child should be unique"
+
+    with subtest("Ensure image with only 2 layers can be loaded"):
+        docker.succeed(
+            "docker load --input='${examples.two-layered-image}'"
+        )
+
+    with subtest(
+        "Ensure the bulk layer doesn't miss store paths (regression test for #78744)"
+    ):
+        docker.succeed(
+            "docker load --input='${pkgs.dockerTools.examples.bulk-layer}'",
+            # Ensure the two output paths (ls and hello) are in the layer
+            "docker run bulk-layer ls /bin/hello",
+        )
+
+    with subtest(
+        "Ensure the bulk layer with a base image respects the number of maxLayers"
+    ):
+        docker.succeed(
+            "docker load --input='${pkgs.dockerTools.examples.layered-bulk-layer}'",
+            # Ensure the image runs correctly
+            "docker run layered-bulk-layer ls /bin/hello",
+        )
+
+        # Ensure the image has the correct number of layers
+        assert len(set_of_layers("layered-bulk-layer")) == 4
+
+    with subtest("Ensure only minimal paths are added to the store"):
+        # TODO: make an example that has no store paths, for example by making
+        #       busybox non-self-referential.
+
+        # This check tests that buildLayeredImage can build images that don't need a store.
+        docker.succeed(
+            "docker load --input='${pkgs.dockerTools.examples.no-store-paths}'"
+        )
+
+        docker.succeed("docker run --rm no-store-paths ls / >/dev/console")
+
+        # If busybox isn't self-referential, we need this line
+        #   docker.fail("docker run --rm no-store-paths ls /nix/store >/dev/console")
+        # However, it currently is self-referential, so we check that it is the
+        # only store path.
+        docker.succeed("diff <(docker run --rm no-store-paths ls /nix/store) <(basename ${pkgs.pkgsStatic.busybox}) >/dev/console")
+
+    with subtest("Ensure buildLayeredImage does not change store path contents."):
+        docker.succeed(
+            "docker load --input='${pkgs.dockerTools.examples.filesInStore}'",
+            "docker run --rm file-in-store nix-store --verify --check-contents",
+            "docker run --rm file-in-store |& grep 'some data'",
+        )
+
+    with subtest("Ensure cross compiled image can be loaded and has correct arch."):
+        docker.succeed(
+            "docker load --input='${pkgs.dockerTools.examples.cross}'",
+        )
+        assert (
+            docker.succeed(
+                "docker inspect ${pkgs.dockerTools.examples.cross.imageName} "
+                + "| ${pkgs.jq}/bin/jq -r .[].Architecture"
+            ).strip()
+            == "${if pkgs.stdenv.hostPlatform.system == "aarch64-linux" then "amd64" else "arm64"}"
+        )
+
+    with subtest("buildLayeredImage doesn't dereference /nix/store symlink layers"):
+        docker.succeed(
+            "docker load --input='${examples.layeredStoreSymlink}'",
+            "docker run --rm ${examples.layeredStoreSymlink.imageName} bash -c 'test -L ${examples.layeredStoreSymlink.passthru.symlink}'",
+            "docker rmi ${examples.layeredStoreSymlink.imageName}",
+        )
+
+    with subtest("buildImage supports registry/ prefix in image name"):
+        docker.succeed(
+            "docker load --input='${examples.prefixedImage}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Repository}}' | grep -F '${examples.prefixedImage.imageName}'"
+        )
+
+    with subtest("buildLayeredImage supports registry/ prefix in image name"):
+        docker.succeed(
+            "docker load --input='${examples.prefixedLayeredImage}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Repository}}' | grep -F '${examples.prefixedLayeredImage.imageName}'"
+        )
+
+    with subtest("buildLayeredImage supports running chown with fakeRootCommands"):
+        docker.succeed(
+            "docker load --input='${examples.layeredImageWithFakeRootCommands}'"
+        )
+        docker.succeed(
+            "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/alice | grep -E ^1000$'"
+        )
+
+    with subtest("Ensure docker load on merged images loads all of the constituent images"):
+        docker.succeed(
+            "docker load --input='${examples.mergedBashAndRedis}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Repository}}-{{.Tag}}' | grep -F '${examples.bash.imageName}-${examples.bash.imageTag}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Repository}}-{{.Tag}}' | grep -F '${examples.redis.imageName}-${examples.redis.imageTag}'"
+        )
+        docker.succeed("docker run --rm ${examples.bash.imageName} bash --version")
+        docker.succeed("docker run --rm ${examples.redis.imageName} redis-cli --version")
+        docker.succeed("docker rmi ${examples.bash.imageName}")
+        docker.succeed("docker rmi ${examples.redis.imageName}")
+
+    with subtest(
+        "Ensure docker load on merged images loads all of the constituent images (missing tags)"
+    ):
+        docker.succeed(
+            "docker load --input='${examples.mergedBashNoTagAndRedis}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Repository}}-{{.Tag}}' | grep -F '${examples.bashNoTag.imageName}-${examples.bashNoTag.imageTag}'"
+        )
+        docker.succeed(
+            "docker images --format '{{.Repository}}-{{.Tag}}' | grep -F '${examples.redis.imageName}-${examples.redis.imageTag}'"
+        )
+        # we need to explicitly specify the generated tag here
+        docker.succeed(
+            "docker run --rm ${examples.bashNoTag.imageName}:${examples.bashNoTag.imageTag} bash --version"
+        )
+        docker.succeed("docker run --rm ${examples.redis.imageName} redis-cli --version")
+        docker.succeed("docker rmi ${examples.bashNoTag.imageName}:${examples.bashNoTag.imageTag}")
+        docker.succeed("docker rmi ${examples.redis.imageName}")
+
+    with subtest("mergeImages preserves owners of the original images"):
+        docker.succeed(
+            "docker load --input='${examples.mergedBashFakeRoot}'"
+        )
+        docker.succeed(
+            "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} sh -c 'stat -c '%u' /home/alice | grep -E ^1000$'"
+        )
+
+    with subtest("The image contains store paths referenced by the fakeRootCommands output"):
+        docker.succeed(
+            "docker run --rm ${examples.layeredImageWithFakeRootCommands.imageName} /hello/bin/layeredImageWithFakeRootCommands-hello"
+        )
+
+    with subtest("mergeImage correctly deals with varying compression schemas in inputs"):
+        docker.succeed("docker load --input='${examples.mergeVaryingCompressor}'")
+
+        for sub_image, tag in [
+            ("${examples.redis.imageName}", "${examples.redis.imageTag}"),
+            ("${examples.bashUncompressed.imageName}", "${examples.bashUncompressed.imageTag}"),
+            ("${examples.bashZstdCompressed.imageName}", "${examples.bashZstdCompressed.imageTag}"),
+        ]:
+            docker.succeed(f"docker images --format '{{{{.Repository}}}}-{{{{.Tag}}}}' | grep -F '{sub_image}-{tag}'")
+            docker.succeed(f"docker rmi {sub_image}")
+
+
+    with subtest("exportImage produces a valid tarball"):
+        docker.succeed(
+            "tar -tf ${examples.exportBash} | grep '\./bin/bash' > /dev/null"
+        )
+
+    with subtest("layered image fakeRootCommands with fakechroot works"):
+        docker.succeed("${examples.imageViaFakeChroot} | docker load")
+        docker.succeed("docker run --rm image-via-fake-chroot | grep -i hello")
+        docker.succeed("docker image rm image-via-fake-chroot:latest")
+
+    with subtest("Ensure bare paths in contents are loaded correctly"):
+        docker.succeed(
+            "docker load --input='${examples.build-image-with-path}'",
+            "docker run --rm build-image-with-path bash -c '[[ -e /hello.txt ]]'",
+            "docker rmi build-image-with-path",
+        )
+        docker.succeed(
+            "${examples.layered-image-with-path} | docker load",
+            "docker run --rm layered-image-with-path bash -c '[[ -e /hello.txt ]]'",
+            "docker rmi layered-image-with-path",
+        )
+
+    with subtest("Ensure correct architecture is present in manifests."):
+        docker.succeed("""
+            docker load --input='${examples.build-image-with-architecture}'
+            docker inspect build-image-with-architecture \
+              | ${pkgs.jq}/bin/jq -er '.[] | select(.Architecture=="arm64").Architecture'
+            docker rmi build-image-with-architecture
+        """)
+        docker.succeed("""
+            ${examples.layered-image-with-architecture} | docker load
+            docker inspect layered-image-with-architecture \
+              | ${pkgs.jq}/bin/jq -er '.[] | select(.Architecture=="arm64").Architecture'
+            docker rmi layered-image-with-architecture
+        """)
+
+    with subtest("etc"):
+        docker.succeed("${examples.etc} | docker load")
+        docker.succeed("docker run --rm etc | grep localhost")
+        docker.succeed("docker image rm etc:latest")
+
+    with subtest("image-with-certs"):
+        docker.succeed("<${examples.image-with-certs} docker load")
+        docker.succeed("docker run --rm image-with-certs:latest test -r /etc/ssl/certs/ca-bundle.crt")
+        docker.succeed("docker run --rm image-with-certs:latest test -r /etc/ssl/certs/ca-certificates.crt")
+        docker.succeed("docker run --rm image-with-certs:latest test -r /etc/pki/tls/certs/ca-bundle.crt")
+        docker.succeed("docker image rm image-with-certs:latest")
+
+    with subtest("buildNixShellImage: Can build a basic derivation"):
+        docker.succeed(
+            "${examples.nix-shell-basic} | docker load",
+            "docker run --rm nix-shell-basic bash -c 'buildDerivation && $out/bin/hello' | grep '^Hello, world!$'"
+        )
+
+    with subtest("buildNixShellImage: Runs the shell hook"):
+        docker.succeed(
+            "${examples.nix-shell-hook} | docker load",
+            "docker run --rm -it nix-shell-hook | grep 'This is the shell hook!'"
+        )
+
+    with subtest("buildNixShellImage: Sources stdenv, making build inputs available"):
+        docker.succeed(
+            "${examples.nix-shell-inputs} | docker load",
+            "docker run --rm -it nix-shell-inputs | grep 'Hello, world!'"
+        )
+
+    with subtest("buildNixShellImage: passAsFile works"):
+        docker.succeed(
+            "${examples.nix-shell-pass-as-file} | docker load",
+            "docker run --rm -it nix-shell-pass-as-file | grep 'this is a string'"
+        )
+
+    with subtest("buildNixShellImage: run argument works"):
+        docker.succeed(
+            "${examples.nix-shell-run} | docker load",
+            "docker run --rm -it nix-shell-run | grep 'This shell is not interactive'"
+        )
+
+    with subtest("buildNixShellImage: command argument works"):
+        docker.succeed(
+            "${examples.nix-shell-command} | docker load",
+            "docker run --rm -it nix-shell-command | grep 'This shell is interactive'"
+        )
+
+    with subtest("buildNixShellImage: home directory is writable by default"):
+        docker.succeed(
+            "${examples.nix-shell-writable-home} | docker load",
+            "docker run --rm -it nix-shell-writable-home"
+        )
+
+    with subtest("buildNixShellImage: home directory can be made non-existent"):
+        docker.succeed(
+            "${examples.nix-shell-nonexistent-home} | docker load",
+            "docker run --rm -it nix-shell-nonexistent-home"
+        )
+
+    with subtest("buildNixShellImage: can build derivations"):
+        docker.succeed(
+            "${examples.nix-shell-build-derivation} | docker load",
+            "docker run --rm -it nix-shell-build-derivation"
+        )
+
+    with subtest("streamLayeredImage: chown is persistent in fakeRootCommands"):
+        docker.succeed(
+            "${chownTestImage} | docker load",
+            "docker run --rm ${chownTestImage.imageName} | diff /dev/stdin <(echo 12345:12345)"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/docker.nix b/nixpkgs/nixos/tests/docker.nix
new file mode 100644
index 000000000000..93baa198088b
--- /dev/null
+++ b/nixpkgs/nixos/tests/docker.nix
@@ -0,0 +1,53 @@
+# This test runs docker and checks if simple container starts
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "docker";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ nequissimus offline ];
+  };
+
+  nodes = {
+    docker =
+      { pkgs, ... }:
+        {
+          virtualisation.docker.enable = true;
+          virtualisation.docker.autoPrune.enable = true;
+          virtualisation.docker.package = pkgs.docker;
+
+          users.users = {
+            noprivs = {
+              isNormalUser = true;
+              description = "Can't access the docker daemon";
+              password = "foobar";
+            };
+
+            hasprivs = {
+              isNormalUser = true;
+              description = "Can access the docker daemon";
+              password = "foobar";
+              extraGroups = [ "docker" ];
+            };
+          };
+        };
+    };
+
+  testScript = ''
+    start_all()
+
+    docker.wait_for_unit("sockets.target")
+    docker.succeed("tar cv --files-from /dev/null | docker import - scratchimg")
+    docker.succeed(
+        "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+    )
+    docker.succeed("docker ps | grep sleeping")
+    docker.succeed("sudo -u hasprivs docker ps")
+    docker.fail("sudo -u noprivs docker ps")
+    docker.succeed("docker stop sleeping")
+
+    # Must match version 4 times to ensure client and server git commits and versions are correct
+    docker.succeed('[ $(docker version | grep ${pkgs.docker.version} | wc -l) = "4" ]')
+    docker.succeed("systemctl restart systemd-sysctl")
+    docker.succeed("grep 1 /proc/sys/net/ipv4/conf/all/forwarding")
+    docker.succeed("grep 1 /proc/sys/net/ipv4/conf/default/forwarding")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/documize.nix b/nixpkgs/nixos/tests/documize.nix
new file mode 100644
index 000000000000..3624c0c56769
--- /dev/null
+++ b/nixpkgs/nixos/tests/documize.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "documize";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.jq ];
+
+    services.documize = {
+      enable = true;
+      port = 3000;
+      dbtype = "postgresql";
+      db = "host=localhost port=5432 sslmode=disable user=documize password=documize dbname=documize";
+    };
+
+    systemd.services.documize-server = {
+      after = [ "postgresql.service" ];
+      requires = [ "postgresql.service" ];
+    };
+
+    services.postgresql = {
+      enable = true;
+      initialScript = pkgs.writeText "psql-init" ''
+        CREATE ROLE documize WITH LOGIN PASSWORD 'documize';
+        CREATE DATABASE documize WITH OWNER documize;
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("documize-server.service")
+    machine.wait_for_open_port(3000)
+
+    dbhash = machine.succeed(
+        "curl -f localhost:3000 | grep 'property=\"dbhash' | grep -Po 'content=\"\\K[^\"]*'"
+    )
+
+    dbhash = dbhash.strip()
+
+    machine.succeed(
+        (
+            "curl -X POST"
+            " --data 'dbname=documize'"
+            " --data 'dbhash={}'"
+            " --data 'title=NixOS'"
+            " --data 'message=Docs'"
+            " --data 'firstname=Bob'"
+            " --data 'lastname=Foobar'"
+            " --data 'email=bob.foobar@nixos.org'"
+            " --data 'password=verysafe'"
+            " -f localhost:3000/api/setup"
+        ).format(dbhash)
+    )
+
+    machine.succeed(
+        'test "$(curl -f localhost:3000/api/public/meta | jq ".title" | xargs echo)" = "NixOS"'
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/doh-proxy-rust.nix b/nixpkgs/nixos/tests/doh-proxy-rust.nix
new file mode 100644
index 000000000000..8c743fe77e32
--- /dev/null
+++ b/nixpkgs/nixos/tests/doh-proxy-rust.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "doh-proxy-rust";
+  meta.maintainers = with lib.maintainers; [ stephank ];
+
+  nodes = {
+    machine = { pkgs, lib, ... }: {
+      services.bind = {
+        enable = true;
+        extraOptions = "empty-zones-enable no;";
+        zones = lib.singleton {
+          name = ".";
+          master = true;
+          file = pkgs.writeText "root.zone" ''
+            $TTL 3600
+            . IN SOA ns.example.org. admin.example.org. ( 1 3h 1h 1w 1d )
+            . IN NS ns.example.org.
+            ns.example.org. IN A    192.168.0.1
+          '';
+        };
+      };
+      services.doh-proxy-rust = {
+        enable = true;
+        flags = [
+          "--server-address=127.0.0.1:53"
+        ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    url = "http://localhost:3000/dns-query"
+    query = "AAABAAABAAAAAAAAAm5zB2V4YW1wbGUDb3JnAAABAAE="  # IN A ns.example.org.
+    bin_ip = r"$'\xC0\xA8\x00\x01'"  # 192.168.0.1, as shell binary string
+
+    machine.wait_for_unit("bind.service")
+    machine.wait_for_unit("doh-proxy-rust.service")
+    machine.wait_for_open_port(53)
+    machine.wait_for_open_port(3000)
+    machine.succeed(f"curl --fail -H 'Accept: application/dns-message' '{url}?dns={query}' | grep -F {bin_ip}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/dokuwiki.nix b/nixpkgs/nixos/tests/dokuwiki.nix
new file mode 100644
index 000000000000..ce3102eec780
--- /dev/null
+++ b/nixpkgs/nixos/tests/dokuwiki.nix
@@ -0,0 +1,161 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  template-bootstrap3 = pkgs.stdenv.mkDerivation rec {
+    name = "bootstrap3";
+    version = "2022-07-27";
+    src = pkgs.fetchFromGitHub {
+      owner = "giterlizzi";
+      repo = "dokuwiki-template-bootstrap3";
+      rev = "v${version}";
+      hash = "sha256-B3Yd4lxdwqfCnfmZdp+i/Mzwn/aEuZ0ovagDxuR6lxo=";
+    };
+    installPhase = "mkdir -p $out; cp -R * $out/";
+  };
+
+
+  plugin-icalevents = pkgs.stdenv.mkDerivation rec {
+    name = "icalevents";
+    version = "2017-06-16";
+    src = pkgs.fetchzip {
+      stripRoot = false;
+      url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/${version}/dokuwiki-plugin-icalevents-${version}.zip";
+      hash = "sha256-IPs4+qgEfe8AAWevbcCM9PnyI0uoyamtWeg4rEb+9Wc=";
+    };
+    installPhase = "mkdir -p $out; cp -R * $out/";
+  };
+
+  acronymsFile = pkgs.writeText "acronyms.local.conf" ''
+    r13y  reproducibility
+  '';
+
+  dwWithAcronyms = pkgs.dokuwiki.overrideAttrs (prev: {
+    installPhase = prev.installPhase or "" + ''
+      ln -sf ${acronymsFile} $out/share/dokuwiki/conf/acronyms.local.conf
+    '';
+  });
+
+  mkNode = webserver: { ... }: {
+    services.dokuwiki = {
+      inherit webserver;
+
+      sites = {
+        "site1.local" = {
+          templates = [ template-bootstrap3 ];
+          settings = {
+            useacl = false;
+            userewrite = true;
+            template = "bootstrap3";
+          };
+        };
+        "site2.local" = {
+          package = dwWithAcronyms;
+          usersFile = "/var/lib/dokuwiki/site2.local/users.auth.php";
+          plugins = [ plugin-icalevents ];
+          settings = {
+            useacl = true;
+            superuser = "admin";
+            title._file = titleFile;
+            plugin.dummy.empty = "This is just for testing purposes";
+          };
+          acl = [
+            { page = "*";
+              actor = "@ALL";
+              level = "read"; }
+            { page = "acl-test";
+              actor = "@ALL";
+              level = "none"; }
+          ];
+          pluginsConfig = {
+            authad = false;
+            authldap = false;
+            authmysql = false;
+            authpgsql = false;
+            tag = false;
+            icalevents = true;
+          };
+        };
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
+    networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+  };
+
+  titleFile = pkgs.writeText "dokuwiki-title" "DokuWiki on site2";
+in {
+  name = "dokuwiki";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [
+      _1000101
+      onny
+      e1mo
+    ];
+  };
+
+  nodes = {
+    dokuwiki_nginx = mkNode "nginx";
+    dokuwiki_caddy = mkNode "caddy";
+  };
+
+  testScript = ''
+
+    start_all()
+
+    dokuwiki_nginx.wait_for_unit("nginx")
+    dokuwiki_caddy.wait_for_unit("caddy")
+
+    site_names = ["site1.local", "site2.local"]
+
+    for machine in (dokuwiki_nginx, dokuwiki_caddy):
+      for site_name in site_names:
+        machine.wait_for_unit(f"phpfpm-dokuwiki-{site_name}")
+
+        machine.succeed("curl -sSfL http://site1.local/ | grep 'DokuWiki'")
+        machine.fail("curl -sSfL 'http://site1.local/doku.php?do=login' | grep 'Login'")
+
+        machine.succeed("curl -sSfL http://site2.local/ | grep 'DokuWiki on site2'")
+        machine.succeed("curl -sSfL 'http://site2.local/doku.php?do=login' | grep 'Login'")
+
+        with subtest("ACL Operations"):
+          machine.succeed(
+            "echo 'admin:$2y$10$ijdBQMzSVV20SrKtCna8gue36vnsbVm2wItAXvdm876sshI4uwy6S:Admin:admin@example.test:user' >> /var/lib/dokuwiki/site2.local/users.auth.php",
+            "curl -sSfL -d 'u=admin&p=password' --cookie-jar cjar 'http://site2.local/doku.php?do=login'",
+            "curl -sSfL --cookie cjar --cookie-jar cjar 'http://site2.local/doku.php?do=login' | grep 'Logged in as: <bdi>Admin</bdi>'",
+          )
+
+          # Ensure the generated ACL is valid
+          machine.succeed(
+            "echo 'No Hello World! for @ALL here' >> /var/lib/dokuwiki/site2.local/data/pages/acl-test.txt",
+            "curl -sSL 'http://site2.local/doku.php?id=acl-test' | grep 'Permission Denied'"
+          )
+
+        with subtest("Customizing Dokuwiki"):
+          machine.succeed(
+            "echo 'r13y is awesome!' >> /var/lib/dokuwiki/site2.local/data/pages/acronyms-test.txt",
+            "curl -sSfL 'http://site2.local/doku.php?id=acronyms-test' | grep '<abbr title=\"reproducibility\">r13y</abbr>'",
+          )
+
+          # Testing if plugins (a) be correctly loaded and (b) configuration to enable them works
+          machine.succeed(
+              "echo '~~INFO:syntaxplugins~~' >> /var/lib/dokuwiki/site2.local/data/pages/plugin-list.txt",
+              "curl -sSfL 'http://site2.local/doku.php?id=plugin-list' | grep 'plugin:icalevents'",
+              "curl -sSfL 'http://site2.local/doku.php?id=plugin-list' | (! grep 'plugin:tag')",
+          )
+
+          # Test if theme is applied and working correctly (no weird relative PHP import errors)
+          machine.succeed(
+            "curl -sSfL 'http://site1.local/doku.php' | grep 'bootstrap3/images/logo.png'",
+            "curl -sSfL 'http://site1.local/lib/exe/css.php' | grep 'bootstrap3'",
+            "curl -sSfL 'http://site1.local/lib/tpl/bootstrap3/css.php'",
+          )
+
+
+        # Just to ensure both Webserver configurations are consistent in allowing that
+        with subtest("Rewriting"):
+          machine.succeed(
+            "echo 'Hello, NixOS!' >> /var/lib/dokuwiki/site1.local/data/pages/rewrite-test.txt",
+            "curl -sSfL http://site1.local/rewrite-test | grep 'Hello, NixOS!'",
+          )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/dolibarr.nix b/nixpkgs/nixos/tests/dolibarr.nix
new file mode 100644
index 000000000000..95557d317fab
--- /dev/null
+++ b/nixpkgs/nixos/tests/dolibarr.nix
@@ -0,0 +1,59 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "dolibarr";
+  meta.maintainers = [ ];
+
+  nodes.machine =
+    { ... }:
+    {
+      services.dolibarr = {
+        enable = true;
+        domain = "localhost";
+        nginx = {
+          forceSSL = false;
+          enableACME = false;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+    };
+
+  testScript = ''
+    from html.parser import HTMLParser
+    start_all()
+
+    csrf_token = None
+    class TokenParser(HTMLParser):
+      def handle_starttag(self, tag, attrs):
+        attrs = dict(attrs) # attrs is an assoc list originally
+        if tag == 'input' and attrs.get('name') == 'token':
+            csrf_token = attrs.get('value')
+            print(f'[+] Caught CSRF token: {csrf_token}')
+      def handle_endtag(self, tag): pass
+      def handle_data(self, data): pass
+
+    machine.wait_for_unit("phpfpm-dolibarr.service")
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_open_port(80)
+    # Sanity checks on URLs.
+    # machine.succeed("curl -fL http://localhost/index.php")
+    # machine.succeed("curl -fL http://localhost/")
+    # Perform installation.
+    machine.succeed('curl -fL -X POST http://localhost/install/check.php -F selectlang=auto')
+    machine.succeed('curl -fL -X POST http://localhost/install/fileconf.php -F selectlang=auto')
+    # First time is to write the configuration file correctly.
+    machine.succeed('curl -fL -X POST http://localhost/install/step1.php -F "testpost=ok" -F "action=set" -F "selectlang=auto"')
+    # Now, we have a proper conf.php in $stateDir.
+    assert 'nixos' in machine.succeed("cat /var/lib/dolibarr/conf.php")
+    machine.succeed('curl -fL -X POST http://localhost/install/step2.php --data "testpost=ok&action=set&dolibarr_main_db_character_set=utf8&dolibarr_main_db_collation=utf8_unicode_ci&selectlang=auto"')
+    machine.succeed('curl -fL -X POST http://localhost/install/step4.php --data "testpost=ok&action=set&selectlang=auto"')
+    machine.succeed('curl -fL -X POST http://localhost/install/step5.php --data "testpost=ok&action=set&login=root&pass=hunter2&pass_verif=hunter2&selectlang=auto"')
+    # Now, we have installed the machine, let's verify we still have the right configuration.
+    assert 'nixos' in machine.succeed("cat /var/lib/dolibarr/conf.php")
+    # We do not want any redirect now as we have installed the machine.
+    machine.succeed('curl -f -X GET http://localhost')
+    # Test authentication to the webservice.
+    parser = TokenParser()
+    parser.feed(machine.succeed('curl -f -X GET http://localhost/index.php?mainmenu=login&username=root'))
+    machine.succeed(f'curl -f -X POST http://localhost/index.php?mainmenu=login&token={csrf_token}&username=root&password=hunter2')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/domination.nix b/nixpkgs/nixos/tests/domination.nix
new file mode 100644
index 000000000000..409a7f3029c4
--- /dev/null
+++ b/nixpkgs/nixos/tests/domination.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "domination";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.domination ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("domination >&2 &")
+      machine.wait_for_window("Menu")
+      machine.wait_for_text(r"(New Game|Start Server|Load Game|Help Manual|Join Game|About|Play Online)")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/dovecot.nix b/nixpkgs/nixos/tests/dovecot.nix
new file mode 100644
index 000000000000..5439387807fd
--- /dev/null
+++ b/nixpkgs/nixos/tests/dovecot.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix {
+  name = "dovecot";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix.enable = true;
+    services.dovecot2 = {
+      enable = true;
+      protocols = [ "imap" "pop3" ];
+      modules = [ pkgs.dovecot_pigeonhole ];
+      mailUser = "vmail";
+      mailGroup = "vmail";
+    };
+    environment.systemPackages = let
+      sendTestMail = pkgs.writeScriptBin "send-testmail" ''
+        #!${pkgs.runtimeShell}
+        exec sendmail -vt <<MAIL
+        From: root@localhost
+        To: alice@localhost
+        Subject: Very important!
+
+        Hello world!
+        MAIL
+      '';
+
+      sendTestMailViaDeliveryAgent = pkgs.writeScriptBin "send-lda" ''
+        #!${pkgs.runtimeShell}
+
+        exec ${pkgs.dovecot}/libexec/dovecot/deliver -d bob <<MAIL
+        From: root@localhost
+        To: bob@localhost
+        Subject: Something else...
+
+        I'm running short of ideas!
+        MAIL
+      '';
+
+      testImap = pkgs.writeScriptBin "test-imap" ''
+        #!${pkgs.python3.interpreter}
+        import imaplib
+
+        with imaplib.IMAP4('localhost') as imap:
+          imap.login('alice', 'foobar')
+          imap.select()
+          status, refs = imap.search(None, 'ALL')
+          assert status == 'OK'
+          assert len(refs) == 1
+          status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+          assert status == 'OK'
+          assert msg[0][1].strip() == b'Hello world!'
+      '';
+
+      testPop = pkgs.writeScriptBin "test-pop" ''
+        #!${pkgs.python3.interpreter}
+        import poplib
+
+        pop = poplib.POP3('localhost')
+        try:
+          pop.user('bob')
+          pop.pass_('foobar')
+          assert len(pop.list()[1]) == 1
+          status, fullmail, size = pop.retr(1)
+          assert status.startswith(b'+OK ')
+          body = b"".join(fullmail[fullmail.index(b""):]).strip()
+          assert body == b"I'm running short of ideas!"
+        finally:
+          pop.quit()
+      '';
+
+    in [ sendTestMail sendTestMailViaDeliveryAgent testImap testPop ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("postfix.service")
+    machine.wait_for_unit("dovecot2.service")
+    machine.succeed("send-testmail")
+    machine.succeed("send-lda")
+    machine.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
+    machine.succeed("test-imap")
+    machine.succeed("test-pop")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/drawterm.nix b/nixpkgs/nixos/tests/drawterm.nix
new file mode 100644
index 000000000000..1d444bb55433
--- /dev/null
+++ b/nixpkgs/nixos/tests/drawterm.nix
@@ -0,0 +1,58 @@
+{ system, pkgs }:
+let
+  tests = {
+    xorg = {
+      node = { pkgs, ... }: {
+        imports = [ ./common/user-account.nix ./common/x11.nix ];
+        services.xserver.enable = true;
+        services.xserver.displayManager.sessionCommands = ''
+          ${pkgs.drawterm}/bin/drawterm -g 1024x768 &
+        '';
+        test-support.displayManager.auto.user = "alice";
+      };
+      systems = [ "x86_64-linux" "aarch64-linux" ];
+    };
+    wayland = {
+      node = { pkgs, ... }: {
+        imports = [ ./common/wayland-cage.nix ];
+        services.cage.program = "${pkgs.drawterm-wayland}/bin/drawterm";
+      };
+      systems = [ "x86_64-linux" ];
+    };
+  };
+
+  mkTest = name: machine:
+    import ./make-test-python.nix ({ pkgs, ... }: {
+      inherit name;
+
+      nodes = { "${name}" = machine; };
+
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ moody ];
+      };
+
+      enableOCR = true;
+
+      testScript = ''
+        @polling_condition
+        def drawterm_running():
+            machine.succeed("pgrep drawterm")
+
+        start_all()
+
+        machine.wait_for_unit("graphical.target")
+        drawterm_running.wait() # type: ignore[union-attr]
+        machine.wait_for_text("cpu")
+        machine.send_chars("cpu\n")
+        machine.wait_for_text("auth")
+        machine.send_chars("cpu\n")
+        machine.wait_for_text("ending")
+        machine.screenshot("out.png")
+      '';
+
+    });
+  mkTestOn = systems: name: machine:
+    if pkgs.lib.elem system systems then mkTest name machine
+    else { ... }: { };
+in
+builtins.mapAttrs (k: v: mkTestOn v.systems k v.node { inherit system; }) tests
diff --git a/nixpkgs/nixos/tests/drbd.nix b/nixpkgs/nixos/tests/drbd.nix
new file mode 100644
index 000000000000..bede7206d706
--- /dev/null
+++ b/nixpkgs/nixos/tests/drbd.nix
@@ -0,0 +1,87 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  let
+    drbdPort = 7789;
+
+    drbdConfig =
+      { nodes, ... }:
+      {
+        virtualisation.emptyDiskImages = [ 1 ];
+        networking.firewall.allowedTCPPorts = [ drbdPort ];
+
+        services.drbd = {
+          enable = true;
+          config = ''
+            global {
+              usage-count yes;
+            }
+
+            common {
+              net {
+                protocol C;
+                ping-int 1;
+              }
+            }
+
+            resource r0 {
+              volume 0 {
+                device    /dev/drbd0;
+                disk      /dev/vdb;
+                meta-disk internal;
+              }
+
+              on drbd1 {
+                address ${nodes.drbd1.config.networking.primaryIPAddress}:${toString drbdPort};
+              }
+
+              on drbd2 {
+                address ${nodes.drbd2.config.networking.primaryIPAddress}:${toString drbdPort};
+              }
+            }
+          '';
+        };
+      };
+  in
+  {
+    name = "drbd";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ ryantm astro ];
+    };
+
+    nodes.drbd1 = drbdConfig;
+    nodes.drbd2 = drbdConfig;
+
+    testScript = { nodes }: ''
+      drbd1.start()
+      drbd2.start()
+
+      drbd1.wait_for_unit("network.target")
+      drbd2.wait_for_unit("network.target")
+
+      drbd1.succeed(
+          "drbdadm create-md r0",
+          "drbdadm up r0",
+          "drbdadm primary r0 --force",
+      )
+
+      drbd2.succeed("drbdadm create-md r0", "drbdadm up r0")
+
+      drbd1.succeed(
+          "mkfs.ext4 /dev/drbd0",
+          "mkdir -p /mnt/drbd",
+          "mount /dev/drbd0 /mnt/drbd",
+          "touch /mnt/drbd/hello",
+          "umount /mnt/drbd",
+          "drbdadm secondary r0",
+      )
+      drbd1.sleep(1)
+
+      drbd2.succeed(
+          "drbdadm primary r0",
+          "mkdir -p /mnt/drbd",
+          "mount /dev/drbd0 /mnt/drbd",
+          "ls /mnt/drbd/hello",
+      )
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/dublin-traceroute.nix b/nixpkgs/nixos/tests/dublin-traceroute.nix
new file mode 100644
index 000000000000..b359b7fcdd6f
--- /dev/null
+++ b/nixpkgs/nixos/tests/dublin-traceroute.nix
@@ -0,0 +1,63 @@
+# This is a simple distributed test involving a topology with two
+# separate virtual networks - the "inside" and the "outside" - with a
+# client on the inside network, a server on the outside network, and a
+# router connected to both that performs Network Address Translation
+# for the client.
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    routerBase =
+      lib.mkMerge [
+        { virtualisation.vlans = [ 2 1 ];
+          networking.nftables.enable = true;
+          networking.nat.internalIPs = [ "192.168.1.0/24" ];
+          networking.nat.externalInterface = "eth1";
+        }
+      ];
+  in
+  {
+    name = "dublin-traceroute";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ baloo ];
+    };
+
+    nodes.client = { nodes, ... }: {
+      imports = [ ./common/user-account.nix ];
+      virtualisation.vlans = [ 1 ];
+
+      networking.defaultGateway =
+        (builtins.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address;
+      networking.nftables.enable = true;
+
+      programs.dublin-traceroute.enable = true;
+    };
+
+    nodes.router = { ... }: {
+      virtualisation.vlans = [ 2 1 ];
+      networking.nftables.enable = true;
+      networking.nat.internalIPs = [ "192.168.1.0/24" ];
+      networking.nat.externalInterface = "eth1";
+      networking.nat.enable = true;
+    };
+
+    nodes.server = { ... }: {
+      virtualisation.vlans = [ 2 ];
+      networking.firewall.enable = false;
+      services.httpd.enable = true;
+      services.httpd.adminAddr = "foo@example.org";
+      services.vsftpd.enable = true;
+      services.vsftpd.anonymousUser = true;
+    };
+
+    testScript = ''
+      client.start()
+      router.start()
+      server.start()
+
+      server.wait_for_unit("network.target")
+      router.wait_for_unit("network.target")
+      client.wait_for_unit("network.target")
+
+      # Make sure we can trace from an unprivileged user
+      client.succeed("sudo -u alice dublin-traceroute server")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/early-mount-options.nix b/nixpkgs/nixos/tests/early-mount-options.nix
new file mode 100644
index 000000000000..8be318ae13bc
--- /dev/null
+++ b/nixpkgs/nixos/tests/early-mount-options.nix
@@ -0,0 +1,19 @@
+# Test for https://github.com/NixOS/nixpkgs/pull/193469
+import ./make-test-python.nix {
+  name = "early-mount-options";
+
+  nodes.machine = {
+    virtualisation.fileSystems."/var" = {
+      options = [ "bind" "nosuid" "nodev" "noexec" ];
+      device = "/var";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    var_mount_info = machine.succeed("findmnt /var -n -o OPTIONS")
+    options = var_mount_info.strip().split(",")
+    assert "nosuid" in options and "nodev" in options and "noexec" in options
+  '';
+}
diff --git a/nixpkgs/nixos/tests/earlyoom.nix b/nixpkgs/nixos/tests/earlyoom.nix
new file mode 100644
index 000000000000..75bdf56899b3
--- /dev/null
+++ b/nixpkgs/nixos/tests/earlyoom.nix
@@ -0,0 +1,16 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "earlyoom";
+  meta = {
+    maintainers = with lib.maintainers; [ ncfavier ];
+  };
+
+  machine = {
+    services.earlyoom = {
+      enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("earlyoom.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ec2.nix b/nixpkgs/nixos/tests/ec2.nix
new file mode 100644
index 000000000000..e649761d029d
--- /dev/null
+++ b/nixpkgs/nixos/tests/ec2.nix
@@ -0,0 +1,156 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+with import common/ec2.nix { inherit makeTest pkgs; };
+
+let
+  imageCfg = (import ../lib/eval-config.nix {
+    inherit system;
+    modules = [
+      ../maintainers/scripts/ec2/amazon-image.nix
+      ../modules/testing/test-instrumentation.nix
+      ../modules/profiles/qemu-guest.nix
+      {
+        # Hack to make the partition resizing work in QEMU.
+        boot.initrd.postDeviceCommands = mkBefore ''
+          ln -s vda /dev/xvda
+          ln -s vda1 /dev/xvda1
+        '';
+
+        # In a NixOS test the serial console is occupied by the "backdoor"
+        # (see testing/test-instrumentation.nix) and is incompatible with
+        # the configuration in virtualisation/amazon-image.nix.
+        systemd.services."serial-getty@ttyS0".enable = mkForce false;
+
+        # Needed by nixos-rebuild due to the lack of network
+        # access. Determined by trial and error.
+        system.extraDependencies = with pkgs; ( [
+          # Needed for a nixos-rebuild.
+          busybox
+          cloud-utils
+          desktop-file-utils
+          libxslt.bin
+          mkinitcpio-nfs-utils
+          stdenv
+          stdenvNoCC
+          texinfo
+          unionfs-fuse
+          xorg.lndir
+
+          # These are used in the configure-from-userdata tests
+          # for EC2. Httpd and valgrind are requested by the
+          # configuration.
+          apacheHttpd
+          apacheHttpd.doc
+          apacheHttpd.man
+          valgrind.doc
+        ]);
+      }
+    ];
+  }).config;
+  image = "${imageCfg.system.build.amazonImage}/${imageCfg.amazonImage.name}.vhd";
+
+  sshKeys = import ./ssh-keys.nix pkgs;
+  snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text;
+  snakeOilPrivateKeyFile = pkgs.writeText "private-key" snakeOilPrivateKey;
+  snakeOilPublicKey = sshKeys.snakeOilPublicKey;
+
+in {
+  boot-ec2-nixops = makeEc2Test {
+    name         = "nixops-userdata";
+    inherit image;
+    sshPublicKey = snakeOilPublicKey; # That's right folks! My user's key is also the host key!
+
+    userData = ''
+      SSH_HOST_ED25519_KEY_PUB:${snakeOilPublicKey}
+      SSH_HOST_ED25519_KEY:${replaceStrings ["\n"] ["|"] snakeOilPrivateKey}
+    '';
+    script = ''
+      machine.start()
+      machine.wait_for_file("/etc/ec2-metadata/user-data")
+      machine.wait_for_unit("sshd.service")
+
+      machine.succeed("grep unknown /etc/ec2-metadata/ami-manifest-path")
+
+      # We have no keys configured on the client side yet, so this should fail
+      machine.fail("ssh -o BatchMode=yes localhost exit")
+
+      # Let's install our client private key
+      machine.succeed("mkdir -p ~/.ssh")
+
+      machine.copy_from_host_via_shell(
+          "${snakeOilPrivateKeyFile}", "~/.ssh/id_ed25519"
+      )
+      machine.succeed("chmod 600 ~/.ssh/id_ed25519")
+
+      # We haven't configured the host key yet, so this should still fail
+      machine.fail("ssh -o BatchMode=yes localhost exit")
+
+      # Add the host key; ssh should finally succeed
+      machine.succeed(
+          "echo localhost,127.0.0.1 ${snakeOilPublicKey} > ~/.ssh/known_hosts"
+      )
+      machine.succeed("ssh -o BatchMode=yes localhost exit")
+
+      # Test whether the root disk was resized.
+      blocks, block_size = map(int, machine.succeed("stat -c %b:%S -f /").split(":"))
+      GB = 1024 ** 3
+      assert 9.7 * GB <= blocks * block_size <= 10 * GB
+
+      # Just to make sure resizing is idempotent.
+      machine.shutdown()
+      machine.start()
+      machine.wait_for_file("/etc/ec2-metadata/user-data")
+    '';
+  };
+
+  boot-ec2-config = makeEc2Test {
+    name         = "config-userdata";
+    meta.broken = true; # amazon-init wants to download from the internet while building the system
+    inherit image;
+    sshPublicKey = snakeOilPublicKey;
+
+    # ### https://nixos.org/channels/nixos-unstable nixos
+    userData = ''
+      { pkgs, ... }:
+
+      {
+        imports = [
+          <nixpkgs/nixos/modules/virtualisation/amazon-image.nix>
+          <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          <nixpkgs/nixos/modules/profiles/qemu-guest.nix>
+        ];
+        environment.etc.testFile = {
+          text = "whoa";
+        };
+
+        networking.hostName = "ec2-test-vm"; # required by services.httpd
+
+        services.httpd = {
+          enable = true;
+          adminAddr = "test@example.org";
+          virtualHosts.localhost.documentRoot = "''${pkgs.valgrind.doc}/share/doc/valgrind/html";
+        };
+        networking.firewall.allowedTCPPorts = [ 80 ];
+      }
+    '';
+    script = ''
+      machine.start()
+
+      # amazon-init must succeed. if it fails, make the test fail
+      # immediately instead of timing out in wait_for_file.
+      machine.wait_for_unit("amazon-init.service")
+
+      machine.wait_for_file("/etc/testFile")
+      assert "whoa" in machine.succeed("cat /etc/testFile")
+
+      machine.wait_for_unit("httpd.service")
+      assert "Valgrind" in machine.succeed("curl http://localhost")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/ecryptfs.nix b/nixpkgs/nixos/tests/ecryptfs.nix
new file mode 100644
index 000000000000..1c67d307a00e
--- /dev/null
+++ b/nixpkgs/nixos/tests/ecryptfs.nix
@@ -0,0 +1,85 @@
+import ./make-test-python.nix ({ ... }:
+{
+  name = "ecryptfs";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    boot.kernelModules = [ "ecryptfs" ];
+    security.pam.enableEcryptfs = true;
+    environment.systemPackages = with pkgs; [ keyutils ];
+  };
+
+  testScript = ''
+    def login_as_alice():
+        machine.wait_until_tty_matches("1", "login: ")
+        machine.send_chars("alice\n")
+        machine.wait_until_tty_matches("1", "Password: ")
+        machine.send_chars("foobar\n")
+        machine.wait_until_tty_matches("1", "alice\@machine")
+
+
+    def logout():
+        machine.send_chars("logout\n")
+        machine.wait_until_tty_matches("1", "login: ")
+
+
+    machine.wait_for_unit("default.target")
+
+    with subtest("Set alice up with a password and a home"):
+        machine.succeed("(echo foobar; echo foobar) | passwd alice")
+        machine.succeed("chown -R alice.users ~alice")
+
+    with subtest("Migrate alice's home"):
+        out = machine.succeed("echo foobar | ecryptfs-migrate-home -u alice")
+        machine.log(f"ecryptfs-migrate-home said: {out}")
+
+    with subtest("Log alice in (ecryptfs passwhrase is wrapped during first login)"):
+        login_as_alice()
+        machine.send_chars("logout\n")
+        machine.wait_until_tty_matches("1", "login: ")
+
+    # Why do I need to do this??
+    machine.succeed("su alice -c ecryptfs-umount-private || true")
+    machine.sleep(1)
+
+    with subtest("check that encrypted home is not mounted"):
+        machine.fail("mount | grep ecryptfs")
+
+    with subtest("Show contents of the user keyring"):
+        out = machine.succeed("su - alice -c 'keyctl list \@u'")
+        machine.log(f"keyctl unlink said: {out}")
+
+    with subtest("Log alice again"):
+        login_as_alice()
+
+    with subtest("Create some files in encrypted home"):
+        machine.succeed("su alice -c 'touch ~alice/a'")
+        machine.succeed("su alice -c 'echo c > ~alice/b'")
+
+    with subtest("Logout"):
+        logout()
+
+    # Why do I need to do this??
+    machine.succeed("su alice -c ecryptfs-umount-private || true")
+    machine.sleep(1)
+
+    with subtest("Check that the filesystem is not accessible"):
+        machine.fail("mount | grep ecryptfs")
+        machine.succeed("su alice -c 'test \! -f ~alice/a'")
+        machine.succeed("su alice -c 'test \! -f ~alice/b'")
+
+    with subtest("Log alice once more"):
+        login_as_alice()
+
+    with subtest("Check that the files are there"):
+        machine.sleep(1)
+        machine.succeed("su alice -c 'test -f ~alice/a'")
+        machine.succeed("su alice -c 'test -f ~alice/b'")
+        machine.succeed('test "$(cat ~alice/b)" = "c"')
+
+    with subtest("Catch https://github.com/NixOS/nixpkgs/issues/16766"):
+        machine.succeed("su alice -c 'ls -lh ~alice/'")
+
+    logout()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/elk.nix b/nixpkgs/nixos/tests/elk.nix
new file mode 100644
index 000000000000..b5a8cb532ae0
--- /dev/null
+++ b/nixpkgs/nixos/tests/elk.nix
@@ -0,0 +1,276 @@
+# To run the test on the unfree ELK use the following command:
+# cd path/to/nixpkgs
+# NIXPKGS_ALLOW_UNFREE=1 nix-build -A nixosTests.elk.unfree.ELK-7
+
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+}:
+
+let
+  inherit (pkgs) lib;
+
+  esUrl = "http://localhost:9200";
+
+  mkElkTest = name : elk :
+    import ./make-test-python.nix ({
+    inherit name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ eelco offline basvandijk ];
+    };
+    nodes = {
+      one =
+        { pkgs, lib, ... }: {
+            # Not giving the machine at least 2060MB results in elasticsearch failing with the following error:
+            #
+            #   OpenJDK 64-Bit Server VM warning:
+            #     INFO: os::commit_memory(0x0000000085330000, 2060255232, 0)
+            #     failed; error='Cannot allocate memory' (errno=12)
+            #
+            #   There is insufficient memory for the Java Runtime Environment to continue.
+            #   Native memory allocation (mmap) failed to map 2060255232 bytes for committing reserved memory.
+            #
+            # When setting this to 2500 I got "Kernel panic - not syncing: Out of
+            # memory: compulsory panic_on_oom is enabled" so lets give it even a
+            # bit more room:
+            virtualisation.memorySize = 3000;
+
+            # For querying JSON objects returned from elasticsearch and kibana.
+            environment.systemPackages = [ pkgs.jq ];
+
+            services = {
+
+              journalbeat = {
+                enable = elk ? journalbeat;
+                package = elk.journalbeat;
+                extraConfig = pkgs.lib.mkOptionDefault (''
+                  logging:
+                    to_syslog: true
+                    level: warning
+                    metrics.enabled: false
+                  output.elasticsearch:
+                    hosts: [ "127.0.0.1:9200" ]
+                  journalbeat.inputs:
+                  - paths: []
+                    seek: cursor
+                '');
+              };
+
+              filebeat = {
+                enable = elk ? filebeat;
+                package = elk.filebeat;
+                inputs.journald.id = "everything";
+
+                inputs.log = {
+                  enabled = true;
+                  paths = [
+                    "/var/lib/filebeat/test"
+                  ];
+                };
+
+                settings = {
+                  logging.level = "info";
+                };
+              };
+
+              metricbeat = {
+                enable = true;
+                package = elk.metricbeat;
+                modules.system = {
+                  metricsets = ["cpu" "load" "memory" "network" "process" "process_summary" "uptime" "socket_summary"];
+                  enabled = true;
+                  period = "5s";
+                  processes = [".*"];
+                  cpu.metrics = ["percentages" "normalized_percentages"];
+                  core.metrics = ["percentages"];
+                };
+                settings = {
+                  output.elasticsearch = {
+                    hosts = ["127.0.0.1:9200"];
+                  };
+                };
+              };
+
+              logstash = {
+                enable = true;
+                package = elk.logstash;
+                inputConfig = ''
+                  exec { command => "echo -n flowers" interval => 1 type => "test" }
+                  exec { command => "echo -n dragons" interval => 1 type => "test" }
+                '';
+                filterConfig = ''
+                  if [message] =~ /dragons/ {
+                    drop {}
+                  }
+                '';
+                outputConfig = ''
+                  file {
+                    path => "/tmp/logstash.out"
+                    codec => line { format => "%{message}" }
+                  }
+                  elasticsearch {
+                    hosts => [ "${esUrl}" ]
+                  }
+                '';
+              };
+
+              elasticsearch = {
+                enable = true;
+                package = elk.elasticsearch;
+              };
+
+              elasticsearch-curator = {
+                enable = elk ? elasticsearch-curator;
+                actionYAML = ''
+                ---
+                actions:
+                  1:
+                    action: delete_indices
+                    description: >-
+                      Delete indices older than 1 second (based on index name), for logstash-
+                      prefixed indices. Ignore the error if the filter does not result in an
+                      actionable list of indices (ignore_empty_list) and exit cleanly.
+                    options:
+                      allow_ilm_indices: true
+                      ignore_empty_list: True
+                      disable_action: False
+                    filters:
+                    - filtertype: pattern
+                      kind: prefix
+                      value: logstash-
+                    - filtertype: age
+                      source: name
+                      direction: older
+                      timestring: '%Y.%m.%d'
+                      unit: seconds
+                      unit_count: 1
+                '';
+              };
+            };
+          };
+      };
+
+    passthru.elkPackages = elk;
+    testScript =
+      let
+        valueObject = lib.optionalString (lib.versionAtLeast elk.elasticsearch.version "7") ".value";
+      in ''
+      import json
+
+
+      def expect_hits(message):
+          dictionary = {"query": {"match": {"message": message}}}
+          return (
+              "curl --silent --show-error --fail-with-body '${esUrl}/_search' "
+              + "-H 'Content-Type: application/json' "
+              + "-d '{}' ".format(json.dumps(dictionary))
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
+          )
+
+
+      def expect_no_hits(message):
+          dictionary = {"query": {"match": {"message": message}}}
+          return (
+              "curl --silent --show-error --fail-with-body '${esUrl}/_search' "
+              + "-H 'Content-Type: application/json' "
+              + "-d '{}' ".format(json.dumps(dictionary))
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} == 0 end'"
+          )
+
+
+      def has_metricbeat():
+          dictionary = {"query": {"match": {"event.dataset": {"query": "system.cpu"}}}}
+          return (
+              "curl --silent --show-error --fail-with-body '${esUrl}/_search' "
+              + "-H 'Content-Type: application/json' "
+              + "-d '{}' ".format(json.dumps(dictionary))
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
+          )
+
+
+      start_all()
+
+      one.wait_for_unit("elasticsearch.service")
+      one.wait_for_open_port(9200)
+
+      # Continue as long as the status is not "red". The status is probably
+      # "yellow" instead of "green" because we are using a single elasticsearch
+      # node which elasticsearch considers risky.
+      #
+      # TODO: extend this test with multiple elasticsearch nodes
+      #       and see if the status turns "green".
+      one.wait_until_succeeds(
+          "curl --silent --show-error --fail-with-body '${esUrl}/_cluster/health'"
+          + " | jq -es 'if . == [] then null else .[] | .status != \"red\" end'"
+      )
+
+      with subtest("Perform some simple logstash tests"):
+          one.wait_for_unit("logstash.service")
+          one.wait_until_succeeds("cat /tmp/logstash.out | grep flowers")
+          one.wait_until_succeeds("cat /tmp/logstash.out | grep -v dragons")
+
+      with subtest("Metricbeat is running"):
+          one.wait_for_unit("metricbeat.service")
+
+      with subtest("Metricbeat metrics arrive in elasticsearch"):
+          one.wait_until_succeeds(has_metricbeat())
+
+      with subtest("Logstash messages arive in elasticsearch"):
+          one.wait_until_succeeds(expect_hits("flowers"))
+          one.wait_until_succeeds(expect_no_hits("dragons"))
+
+    '' + lib.optionalString (elk ? journalbeat) ''
+      with subtest(
+          "A message logged to the journal is ingested by elasticsearch via journalbeat"
+      ):
+          one.wait_for_unit("journalbeat.service")
+          one.execute("echo 'Supercalifragilisticexpialidocious' | systemd-cat")
+          one.wait_until_succeeds(
+              expect_hits("Supercalifragilisticexpialidocious")
+          )
+    '' + lib.optionalString (elk ? filebeat) ''
+      with subtest(
+          "A message logged to the journal is ingested by elasticsearch via filebeat"
+      ):
+          one.wait_for_unit("filebeat.service")
+          one.execute("echo 'Superdupercalifragilisticexpialidocious' | systemd-cat")
+          one.wait_until_succeeds(
+              expect_hits("Superdupercalifragilisticexpialidocious")
+          )
+          one.execute(
+              "echo 'SuperdupercalifragilisticexpialidociousIndeed' >> /var/lib/filebeat/test"
+          )
+          one.wait_until_succeeds(
+              expect_hits("SuperdupercalifragilisticexpialidociousIndeed")
+          )
+    '' + lib.optionalString (elk ? elasticsearch-curator) ''
+      with subtest("Elasticsearch-curator works"):
+          one.systemctl("stop logstash")
+          one.systemctl("start elasticsearch-curator")
+          one.wait_until_succeeds(
+              '! curl --silent --show-error --fail-with-body "${esUrl}/_cat/indices" | grep logstash | grep ^'
+          )
+    '';
+  }) { inherit pkgs system; };
+in {
+  # We currently only package upstream binaries.
+  # Feel free to package an SSPL licensed source-based package!
+  # ELK-7 = mkElkTest "elk-7-oss" {
+  #   name = "elk-7";
+  #   elasticsearch = pkgs.elasticsearch7-oss;
+  #   logstash      = pkgs.logstash7-oss;
+  #   filebeat      = pkgs.filebeat7;
+  #   metricbeat    = pkgs.metricbeat7;
+  # };
+  unfree = lib.dontRecurseIntoAttrs {
+    ELK-7 = mkElkTest "elk-7" {
+      elasticsearch = pkgs.elasticsearch7;
+      logstash      = pkgs.logstash7;
+      filebeat      = pkgs.filebeat7;
+      metricbeat    = pkgs.metricbeat7;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/emacs-daemon.nix b/nixpkgs/nixos/tests/emacs-daemon.nix
new file mode 100644
index 000000000000..310e93e19b0b
--- /dev/null
+++ b/nixpkgs/nixos/tests/emacs-daemon.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "emacs-daemon";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  enableOCR = true;
+
+  nodes.machine =
+    { ... }:
+
+    { imports = [ ./common/x11.nix ];
+      services.emacs = {
+        enable = true;
+        defaultEditor = true;
+      };
+
+      # Important to get the systemd service running for root
+      environment.variables.XDG_RUNTIME_DIR = "/run/user/0";
+
+      environment.variables.TEST_SYSTEM_VARIABLE = "system variable";
+    };
+
+  testScript = ''
+      machine.wait_for_unit("multi-user.target")
+
+      # checks that the EDITOR environment variable is set
+      machine.succeed('test $(basename "$EDITOR") = emacseditor')
+
+      # waits for the emacs service to be ready
+      machine.wait_until_succeeds(
+          "systemctl --user status emacs.service | grep 'Active: active'"
+      )
+
+      # connects to the daemon
+      machine.succeed("emacsclient --no-wait --frame-parameters='((display . \"'\"$DISPLAY\"'\"))' --create-frame $EDITOR >&2")
+
+      # checks that Emacs shows the edited filename
+      machine.wait_for_text("emacseditor")
+
+      # makes sure environment variables are accessible from Emacs
+      machine.succeed(
+          "emacsclient --eval '(getenv \"TEST_SYSTEM_VARIABLE\")' | grep -q 'system variable'"
+      )
+
+      machine.screenshot("emacsclient")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/empty-file b/nixpkgs/nixos/tests/empty-file
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/nixpkgs/nixos/tests/empty-file
diff --git a/nixpkgs/nixos/tests/endlessh-go.nix b/nixpkgs/nixos/tests/endlessh-go.nix
new file mode 100644
index 000000000000..b261dbf1c560
--- /dev/null
+++ b/nixpkgs/nixos/tests/endlessh-go.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "endlessh-go";
+  meta.maintainers = with lib.maintainers; [ azahi ];
+
+  nodes = {
+    server = { ... }: {
+      services.endlessh-go = {
+        enable = true;
+        prometheus.enable = true;
+        openFirewall = true;
+      };
+
+      specialisation = {
+        unprivileged.configuration = {
+          services.endlessh-go = {
+            port = 2222;
+            prometheus.port = 9229;
+          };
+        };
+
+        privileged.configuration = {
+          services.endlessh-go = {
+            port = 22;
+            prometheus.port = 92;
+          };
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ curl netcat ];
+    };
+  };
+
+  testScript = ''
+    def activate_specialisation(name: str):
+        server.succeed(f"/run/booted-system/specialisation/{name}/bin/switch-to-configuration test >&2")
+
+    start_all()
+
+    with subtest("Unprivileged"):
+        activate_specialisation("unprivileged")
+        server.wait_for_unit("endlessh-go.service")
+        server.wait_for_open_port(2222)
+        server.wait_for_open_port(9229)
+        client.succeed("nc -dvW5 server 2222")
+        client.succeed("curl -kv server:9229/metrics")
+
+    with subtest("Privileged"):
+        activate_specialisation("privileged")
+        server.wait_for_unit("endlessh-go.service")
+        server.wait_for_open_port(22)
+        server.wait_for_open_port(92)
+        client.succeed("nc -dvW5 server 22")
+        client.succeed("curl -kv server:92/metrics")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/endlessh.nix b/nixpkgs/nixos/tests/endlessh.nix
new file mode 100644
index 000000000000..be742a749fdd
--- /dev/null
+++ b/nixpkgs/nixos/tests/endlessh.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "endlessh";
+  meta.maintainers = with lib.maintainers; [ azahi ];
+
+  nodes = {
+    server = { ... }: {
+      services.endlessh = {
+        enable = true;
+        openFirewall = true;
+      };
+
+      specialisation = {
+        unprivileged.configuration.services.endlessh.port = 2222;
+
+        privileged.configuration.services.endlessh.port = 22;
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ curl netcat ];
+    };
+  };
+
+  testScript = ''
+    def activate_specialisation(name: str):
+        server.succeed(f"/run/booted-system/specialisation/{name}/bin/switch-to-configuration test >&2")
+
+    start_all()
+
+    with subtest("Unprivileged"):
+        activate_specialisation("unprivileged")
+        server.wait_for_unit("endlessh.service")
+        server.wait_for_open_port(2222)
+        client.succeed("nc -dvW5 server 2222")
+
+    with subtest("Privileged"):
+        activate_specialisation("privileged")
+        server.wait_for_unit("endlessh.service")
+        server.wait_for_open_port(22)
+        client.succeed("nc -dvW5 server 22")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/engelsystem.nix b/nixpkgs/nixos/tests/engelsystem.nix
new file mode 100644
index 000000000000..7be3b8a5a1fe
--- /dev/null
+++ b/nixpkgs/nixos/tests/engelsystem.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  {
+    name = "engelsystem";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ talyz ];
+    };
+
+    nodes.engelsystem =
+      { ... }:
+      {
+        services.engelsystem = {
+          enable = true;
+          domain = "engelsystem";
+          createDatabase = true;
+        };
+        networking.firewall.allowedTCPPorts = [ 80 443 ];
+        environment.systemPackages = with pkgs; [
+          xmlstarlet
+          libxml2
+        ];
+      };
+
+    testScript = ''
+      engelsystem.start()
+      engelsystem.wait_for_unit("phpfpm-engelsystem.service")
+      engelsystem.wait_until_succeeds("curl engelsystem/login -sS -f")
+      engelsystem.succeed(
+          "curl engelsystem/login -sS -f -c cookie | xmllint -html -xmlout - >login"
+      )
+      engelsystem.succeed(
+          "xml sel -T -t -m \"html/head/meta[@name='csrf-token']\" -v @content login >token"
+      )
+      engelsystem.succeed(
+          "curl engelsystem/login -sS -f -b cookie -F 'login=admin' -F 'password=asdfasdf' -F '_token=<token' -L | xmllint -html -xmlout - >news"
+      )
+      engelsystem.succeed(
+          "test 'News - Engelsystem' = \"$(xml sel -T -t -c html/head/title news)\""
+      )
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/enlightenment.nix b/nixpkgs/nixos/tests/enlightenment.nix
new file mode 100644
index 000000000000..bce14c1ddd5c
--- /dev/null
+++ b/nixpkgs/nixos/tests/enlightenment.nix
@@ -0,0 +1,96 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+{
+  name = "enlightenment";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ romildo ];
+  };
+
+  nodes.machine = { ... }:
+  {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.desktopManager.enlightenment.enable = true;
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+    hardware.pulseaudio.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
+    environment.systemPackages = [ pkgs.xdotool ];
+    services.acpid.enable = true;
+    services.connman.enable = true;
+    services.connman.package = pkgs.connmanMinimal;
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+  in ''
+    with subtest("Ensure x starts"):
+        machine.wait_for_x()
+        machine.wait_for_file("${user.home}/.Xauthority")
+        machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+    with subtest("Check that logging in has given the user ownership of devices"):
+        machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+    with subtest("First time wizard"):
+        machine.wait_for_text("Default")  # Language
+        machine.screenshot("wizard1")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+        machine.screenshot("wizard2")
+
+        machine.wait_for_text("English")  # Keyboard (default)
+        machine.screenshot("wizard3")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("Standard")  # Profile (default)
+        machine.screenshot("wizard4")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("Title")  # Sizing (default)
+        machine.screenshot("wizard5")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("clicked")  # Windows Focus
+        machine.succeed("xdotool mousemove 512 370 click 1")  # Click
+        machine.screenshot("wizard6")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("Connman")  # Network Management (default)
+        machine.screenshot("wizard7")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("BlusZ")  # Bluetooth Management (default)
+        machine.screenshot("wizard8")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("OpenGL")  # Compositing (default)
+        machine.screenshot("wizard9")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("update")  # Updates
+        machine.succeed("xdotool mousemove 512 495 click 1")  # Disable
+        machine.screenshot("wizard10")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("taskbar")  # Taskbar
+        machine.succeed("xdotool mousemove 480 410 click 1")  # Enable
+        machine.screenshot("wizard11")
+        machine.succeed("xdotool mousemove 512 740 click 1")  # Next
+
+        machine.wait_for_text("Home")  # The desktop
+        machine.screenshot("wizard12")
+
+    with subtest("Run Terminology"):
+        machine.succeed("terminology >&2 &")
+        machine.sleep(5)
+        machine.send_chars("ls --color -alF\n")
+        machine.sleep(2)
+        machine.screenshot("terminology")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/env.nix b/nixpkgs/nixos/tests/env.nix
new file mode 100644
index 000000000000..dec17b6b565a
--- /dev/null
+++ b/nixpkgs/nixos/tests/env.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "environment";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  nodes.machine = { pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages;
+      environment.etc.plainFile.text = ''
+        Hello World
+      '';
+      environment.etc."folder/with/file".text = ''
+        Foo Bar!
+      '';
+
+      environment.sessionVariables = {
+        TERMINFO_DIRS = "/run/current-system/sw/share/terminfo";
+        NIXCON = "awesome";
+      };
+    };
+
+  testScript = ''
+    machine.succeed('[ -L "/etc/plainFile" ]')
+    assert "Hello World" in machine.succeed('cat "/etc/plainFile"')
+    machine.succeed('[ -d "/etc/folder" ]')
+    machine.succeed('[ -d "/etc/folder/with" ]')
+    machine.succeed('[ -L "/etc/folder/with/file" ]')
+    assert "Hello World" in machine.succeed('cat "/etc/plainFile"')
+
+    assert "/run/current-system/sw/share/terminfo" in machine.succeed(
+        "echo ''${TERMINFO_DIRS}"
+    )
+    assert "awesome" in machine.succeed("echo ''${NIXCON}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/envfs.nix b/nixpkgs/nixos/tests/envfs.nix
new file mode 100644
index 000000000000..3f9cd1edb595
--- /dev/null
+++ b/nixpkgs/nixos/tests/envfs.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+let
+  pythonShebang = pkgs.writeScript "python-shebang" ''
+    #!/usr/bin/python
+    print("OK")
+  '';
+
+  bashShebang = pkgs.writeScript "bash-shebang" ''
+    #!/usr/bin/bash
+    echo "OK"
+  '';
+in
+{
+  name = "envfs";
+  nodes.machine.services.envfs.enable = true;
+
+  testScript = ''
+    start_all()
+    machine.wait_until_succeeds("mountpoint -q /usr/bin/")
+    machine.succeed(
+        "PATH=${pkgs.coreutils}/bin /usr/bin/cp --version",
+        # check fallback paths
+        "PATH= /usr/bin/sh --version",
+        "PATH= /usr/bin/env --version",
+        "PATH= test -e /usr/bin/sh",
+        "PATH= test -e /usr/bin/env",
+        # no stat
+        "! test -e /usr/bin/cp",
+        # also picks up PATH that was set after execve
+        "! /usr/bin/hello",
+        "PATH=${pkgs.hello}/bin /usr/bin/hello",
+    )
+
+    out = machine.succeed("PATH=${pkgs.python3}/bin ${pythonShebang}")
+    print(out)
+    assert out == "OK\n"
+
+    out = machine.succeed("PATH=${pkgs.bash}/bin ${bashShebang}")
+    print(out)
+    assert out == "OK\n"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/envoy.nix b/nixpkgs/nixos/tests/envoy.nix
new file mode 100644
index 000000000000..1e4bfe626398
--- /dev/null
+++ b/nixpkgs/nixos/tests/envoy.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "envoy";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ cameronnemo ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    services.envoy.enable = true;
+    services.envoy.settings = {
+      admin = {
+        access_log_path = "/dev/null";
+        address = {
+          socket_address = {
+            protocol = "TCP";
+            address = "127.0.0.1";
+            port_value = 80;
+          };
+        };
+      };
+      static_resources = {
+        listeners = [];
+        clusters = [];
+      };
+    };
+    specialisation = {
+      withoutConfigValidation.configuration = { ... }: {
+        services.envoy = {
+          requireValidConfig = false;
+          settings.admin.access_log_path = lib.mkForce "/var/log/envoy/access.log";
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+    in
+    ''
+      machine.start()
+
+      with subtest("envoy.service starts and responds with ready"):
+        machine.wait_for_unit("envoy.service")
+        machine.wait_for_open_port(80)
+        machine.wait_until_succeeds("curl -fsS localhost:80/ready")
+
+      with subtest("envoy.service works with config path not available at eval time"):
+        machine.succeed('${specialisations}/withoutConfigValidation/bin/switch-to-configuration test')
+        machine.wait_for_unit("envoy.service")
+        machine.wait_for_open_port(80)
+        machine.wait_until_succeeds("curl -fsS localhost:80/ready")
+        machine.succeed('test -f /var/log/envoy/access.log')
+    '';
+})
diff --git a/nixpkgs/nixos/tests/ergo.nix b/nixpkgs/nixos/tests/ergo.nix
new file mode 100644
index 000000000000..b49e0c9dfed7
--- /dev/null
+++ b/nixpkgs/nixos/tests/ergo.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "ergo";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.ergo.enable = true;
+      services.ergo.api.keyHash = "324dcf027dd4a30a932c441f365a25e86b173defa4b8e58948253471b81b72cf";
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("ergo.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ergochat.nix b/nixpkgs/nixos/tests/ergochat.nix
new file mode 100644
index 000000000000..2e9dc55e648e
--- /dev/null
+++ b/nixpkgs/nixos/tests/ergochat.nix
@@ -0,0 +1,97 @@
+let
+  clients = [
+    "ircclient1"
+    "ircclient2"
+  ];
+  server = "ergochat";
+  ircPort = 6667;
+  channel = "nixos-cat";
+  iiDir = "/tmp/irc";
+in
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "ergochat";
+  nodes = {
+    "${server}" = {
+      networking.firewall.allowedTCPPorts = [ ircPort ];
+      services.ergochat = {
+        enable = true;
+        settings.server.motd = pkgs.writeText "ergo.motd" ''
+          The default MOTD doesn't contain the word "nixos" in it.
+          This one does.
+        '';
+      };
+    };
+  } // lib.listToAttrs (builtins.map (client: lib.nameValuePair client {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    systemd.services.ii = {
+      requires = [ "network.target" ];
+      wantedBy = [ "default.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        ExecPreStartPre = "mkdir -p ${iiDir}";
+        ExecStart = ''
+          ${lib.getBin pkgs.ii}/bin/ii -n ${client} -s ${server} -i ${iiDir}
+        '';
+        User = "alice";
+      };
+    };
+  }) clients);
+
+  testScript =
+    let
+      msg = client: "Hello, my name is ${client}";
+      clientScript = client: [
+        ''
+          ${client}.wait_for_unit("network.target")
+          ${client}.systemctl("start ii")
+          ${client}.wait_for_unit("ii")
+          ${client}.wait_for_file("${iiDir}/${server}/out")
+        ''
+        # look for the custom text in the MOTD.
+        ''
+          ${client}.wait_until_succeeds("grep 'nixos' ${iiDir}/${server}/out")
+        ''
+        # wait until first PING from server arrives before joining,
+        # so we don't try it too early
+        ''
+          ${client}.wait_until_succeeds("grep 'PING' ${iiDir}/${server}/out")
+        ''
+        # join ${channel}
+        ''
+          ${client}.succeed("echo '/j #${channel}' > ${iiDir}/${server}/in")
+          ${client}.wait_for_file("${iiDir}/${server}/#${channel}/in")
+        ''
+        # send a greeting
+        ''
+          ${client}.succeed(
+              "echo '${msg client}' > ${iiDir}/${server}/#${channel}/in"
+          )
+        ''
+        # check that all greetings arrived on all clients
+      ] ++ builtins.map (other: ''
+        ${client}.succeed(
+            "grep '${msg other}$' ${iiDir}/${server}/#${channel}/out"
+        )
+      '') clients;
+
+      # foldl', but requires a non-empty list instead of a start value
+      reduce = f: list:
+        builtins.foldl' f (builtins.head list) (builtins.tail list);
+    in ''
+      start_all()
+      ${server}.systemctl("status ergochat")
+      ${server}.wait_for_open_port(${toString ircPort})
+
+      # run clientScript for all clients so that every list
+      # entry is executed by every client before advancing
+      # to the next one.
+    '' + lib.concatStrings
+      (reduce
+        (lib.zipListsWith (cs: c: cs + c))
+        (builtins.map clientScript clients));
+})
diff --git a/nixpkgs/nixos/tests/eris-server.nix b/nixpkgs/nixos/tests/eris-server.nix
new file mode 100644
index 000000000000..b9d2b57401e0
--- /dev/null
+++ b/nixpkgs/nixos/tests/eris-server.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "eris-server";
+  meta.maintainers = with lib.maintainers; [ ehmry ];
+
+  nodes.server = {
+    environment.systemPackages = [ pkgs.eris-go pkgs.eriscmd ];
+    services.eris-server = {
+      enable = true;
+      decode = true;
+      listenHttp = "[::1]:80";
+      backends = [ "badger+file:///var/cache/eris.badger?get&put" ];
+      mountpoint = "/eris";
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("eris-server.service")
+    server.wait_for_open_port(5683)
+    server.wait_for_open_port(80)
+    server.succeed("eriscmd get http://[::1] $(echo 'Hail ERIS!' | eriscmd put coap+tcp://[::1]:5683)")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/esphome.nix b/nixpkgs/nixos/tests/esphome.nix
new file mode 100644
index 000000000000..5a318b65a723
--- /dev/null
+++ b/nixpkgs/nixos/tests/esphome.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  testPort = 6052;
+  unixSocket = "/run/esphome/esphome.sock";
+in
+{
+  name = "esphome";
+  meta.maintainers = with lib.maintainers; [ oddlama ];
+
+  nodes = {
+    esphomeTcp = { ... }:
+      {
+        services.esphome = {
+          enable = true;
+          port = testPort;
+          address = "0.0.0.0";
+          openFirewall = true;
+        };
+      };
+
+    esphomeUnix = { ... }:
+      {
+        services.esphome = {
+          enable = true;
+          enableUnixSocket = true;
+        };
+      };
+  };
+
+  testScript = ''
+    esphomeTcp.wait_for_unit("esphome.service")
+    esphomeTcp.wait_for_open_port(${toString testPort})
+    esphomeTcp.succeed("curl --fail http://localhost:${toString testPort}/")
+
+    esphomeUnix.wait_for_unit("esphome.service")
+    esphomeUnix.wait_for_file("${unixSocket}")
+    esphomeUnix.succeed("curl --fail --unix-socket ${unixSocket} http://localhost/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/etcd-cluster.nix b/nixpkgs/nixos/tests/etcd-cluster.nix
new file mode 100644
index 000000000000..c77c0dd73c25
--- /dev/null
+++ b/nixpkgs/nixos/tests/etcd-cluster.nix
@@ -0,0 +1,157 @@
+# This test runs simple etcd cluster
+
+import ./make-test-python.nix ({ pkgs, ... } : let
+
+  runWithOpenSSL = file: cmd: pkgs.runCommand file {
+    buildInputs = [ pkgs.openssl ];
+  } cmd;
+
+  ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+  ca_pem = runWithOpenSSL "ca.pem" ''
+    openssl req \
+      -x509 -new -nodes -key ${ca_key} \
+      -days 10000 -out $out -subj "/CN=etcd-ca"
+  '';
+  etcd_key = runWithOpenSSL "etcd-key.pem" "openssl genrsa -out $out 2048";
+  etcd_csr = runWithOpenSSL "etcd.csr" ''
+    openssl req \
+       -new -key ${etcd_key} \
+       -out $out -subj "/CN=etcd" \
+       -config ${openssl_cnf}
+  '';
+  etcd_cert = runWithOpenSSL "etcd.pem" ''
+    openssl x509 \
+      -req -in ${etcd_csr} \
+      -CA ${ca_pem} -CAkey ${ca_key} \
+      -CAcreateserial -out $out \
+      -days 365 -extensions v3_req \
+      -extfile ${openssl_cnf}
+  '';
+
+  etcd_client_key = runWithOpenSSL "etcd-client-key.pem"
+    "openssl genrsa -out $out 2048";
+
+  etcd_client_csr = runWithOpenSSL "etcd-client-key.pem" ''
+    openssl req \
+      -new -key ${etcd_client_key} \
+      -out $out -subj "/CN=etcd-client" \
+      -config ${client_openssl_cnf}
+  '';
+
+  etcd_client_cert = runWithOpenSSL "etcd-client.crt" ''
+    openssl x509 \
+      -req -in ${etcd_client_csr} \
+      -CA ${ca_pem} -CAkey ${ca_key} -CAcreateserial \
+      -out $out -days 365 -extensions v3_req \
+      -extfile ${client_openssl_cnf}
+  '';
+
+  openssl_cnf = pkgs.writeText "openssl.cnf" ''
+    ions = v3_req
+    distinguished_name = req_distinguished_name
+    [req_distinguished_name]
+    [ v3_req ]
+    basicConstraints = CA:FALSE
+    keyUsage = digitalSignature, keyEncipherment
+    extendedKeyUsage = serverAuth, clientAuth
+    subjectAltName = @alt_names
+    [alt_names]
+    DNS.1 = node1
+    DNS.2 = node2
+    DNS.3 = node3
+    IP.1 = 127.0.0.1
+  '';
+
+  client_openssl_cnf = pkgs.writeText "client-openssl.cnf" ''
+    ions = v3_req
+    distinguished_name = req_distinguished_name
+    [req_distinguished_name]
+    [ v3_req ]
+    basicConstraints = CA:FALSE
+    keyUsage = digitalSignature, keyEncipherment
+    extendedKeyUsage = clientAuth
+  '';
+
+  nodeConfig = {
+    services = {
+      etcd = {
+        enable = true;
+        keyFile = etcd_key;
+        certFile = etcd_cert;
+        trustedCaFile = ca_pem;
+        clientCertAuth = true;
+        listenClientUrls = ["https://127.0.0.1:2379"];
+        listenPeerUrls = ["https://0.0.0.0:2380"];
+      };
+    };
+
+    environment.variables = {
+      ETCD_CERT_FILE = "${etcd_client_cert}";
+      ETCD_KEY_FILE = "${etcd_client_key}";
+      ETCD_CA_FILE = "${ca_pem}";
+      ETCDCTL_ENDPOINTS = "https://127.0.0.1:2379";
+      ETCDCTL_CACERT = "${ca_pem}";
+      ETCDCTL_CERT = "${etcd_cert}";
+      ETCDCTL_KEY = "${etcd_key}";
+    };
+
+    networking.firewall.allowedTCPPorts = [ 2380 ];
+  };
+in {
+  name = "etcd-cluster";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    node1 = { ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
+        initialAdvertisePeerUrls = ["https://node1:2380"];
+      };
+    };
+
+    node2 = { ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380"];
+        initialAdvertisePeerUrls = ["https://node2:2380"];
+      };
+    };
+
+    node3 = { ... }: {
+      require = [nodeConfig];
+      services.etcd = {
+        initialCluster = ["node1=https://node1:2380" "node2=https://node2:2380" "node3=https://node3:2380"];
+        initialAdvertisePeerUrls = ["https://node3:2380"];
+        initialClusterState = "existing";
+      };
+    };
+  };
+
+  testScript = ''
+    with subtest("should start etcd cluster"):
+        node1.start()
+        node2.start()
+        node1.wait_for_unit("etcd.service")
+        node2.wait_for_unit("etcd.service")
+        node2.wait_until_succeeds("etcdctl endpoint status")
+        node1.succeed("etcdctl put /foo/bar 'Hello world'")
+        node2.succeed("etcdctl get /foo/bar | grep 'Hello world'")
+
+    with subtest("should add another member"):
+        node1.wait_until_succeeds("etcdctl member add node3 --peer-urls=https://node3:2380")
+        node3.start()
+        node3.wait_for_unit("etcd.service")
+        node3.wait_until_succeeds("etcdctl member list | grep 'node3'")
+        node3.succeed("etcdctl endpoint status")
+
+    with subtest("should survive member crash"):
+        node3.crash()
+        node1.succeed("etcdctl endpoint status")
+        node1.succeed("etcdctl put /foo/bar 'Hello degraded world'")
+        node1.succeed("etcdctl get /foo/bar | grep 'Hello degraded world'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/etcd.nix b/nixpkgs/nixos/tests/etcd.nix
new file mode 100644
index 000000000000..79857778ae1b
--- /dev/null
+++ b/nixpkgs/nixos/tests/etcd.nix
@@ -0,0 +1,25 @@
+# This test runs simple etcd node
+
+import ./make-test-python.nix ({ pkgs, ... } : {
+  name = "etcd";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    node = { ... }: {
+      services.etcd.enable = true;
+    };
+  };
+
+  testScript = ''
+    with subtest("should start etcd node"):
+        node.start()
+        node.wait_for_unit("etcd.service")
+
+    with subtest("should write and read some values to etcd"):
+        node.succeed("etcdctl put /foo/bar 'Hello world'")
+        node.succeed("etcdctl get /foo/bar | grep 'Hello world'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/etebase-server.nix b/nixpkgs/nixos/tests/etebase-server.nix
new file mode 100644
index 000000000000..49bfccf359e2
--- /dev/null
+++ b/nixpkgs/nixos/tests/etebase-server.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  dataDir = "/var/lib/foobar";
+
+in {
+    name = "etebase-server";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ felschr ];
+    };
+
+    nodes.machine = { pkgs, ... }:
+      {
+        services.etebase-server = {
+          inherit dataDir;
+          enable = true;
+          settings.global.secret_file =
+            toString (pkgs.writeText "secret" "123456");
+        };
+      };
+
+    testScript = ''
+      machine.wait_for_unit("etebase-server.service")
+      machine.wait_for_open_port(8001)
+
+      with subtest("Database & src-version were created"):
+          machine.wait_for_file("${dataDir}/src-version")
+          assert (
+              "${pkgs.etebase-server}"
+              in machine.succeed("cat ${dataDir}/src-version")
+          )
+          machine.wait_for_file("${dataDir}/db.sqlite3")
+          machine.wait_for_file("${dataDir}/static")
+
+      with subtest("Only allow access from allowed_hosts"):
+          machine.succeed("curl -sSfL http://0.0.0.0:8001/")
+          machine.fail("curl -sSfL http://127.0.0.1:8001/")
+          machine.fail("curl -sSfL http://localhost:8001/")
+
+      with subtest("Run tests"):
+          machine.succeed("etebase-server check")
+          machine.succeed("etebase-server test")
+
+      with subtest("Create superuser"):
+          machine.succeed(
+              "etebase-server createsuperuser --no-input --username admin --email root@localhost"
+          )
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/etesync-dav.nix b/nixpkgs/nixos/tests/etesync-dav.nix
new file mode 100644
index 000000000000..f49152c60991
--- /dev/null
+++ b/nixpkgs/nixos/tests/etesync-dav.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+
+  name = "etesync-dav";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ _3699n ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+      environment.systemPackages = [ pkgs.curl pkgs.etesync-dav ];
+  };
+
+  testScript =
+    ''
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("etesync-dav --version")
+      machine.execute("etesync-dav >&2 &")
+      machine.wait_for_open_port(37358)
+      with subtest("Check that the web interface is accessible"):
+          assert "Add User" in machine.succeed("curl -s http://localhost:37358/.web/add/")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/evcc.nix b/nixpkgs/nixos/tests/evcc.nix
new file mode 100644
index 000000000000..7ebdc6a6f5ab
--- /dev/null
+++ b/nixpkgs/nixos/tests/evcc.nix
@@ -0,0 +1,96 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+  name = "evcc";
+  meta.maintainers = with lib.maintainers; [ hexa ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.evcc = {
+        enable = true;
+        settings = {
+          network = {
+            schema = "http";
+            host = "localhost";
+            port = 7070;
+          };
+
+          log = "info";
+
+          site = {
+            title = "NixOS Test";
+            meters = {
+              grid = "grid";
+              pv = "pv";
+            };
+          };
+
+          meters = [ {
+            type = "custom";
+            name = "grid";
+            power = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo -4500'";
+            };
+          } {
+            type = "custom";
+            name = "pv";
+            power = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo 7500'";
+            };
+          } ];
+
+          chargers = [ {
+            name = "dummy-charger";
+            type = "custom";
+            status = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo charger status A'";
+            };
+            enabled = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo charger enabled state false'";
+            };
+            enable = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo set charger enabled state true'";
+            };
+            maxcurrent = {
+              source = "script";
+              cmd = "/bin/sh -c 'echo set charger max current 7200'";
+            };
+          } ];
+
+          loadpoints = [ {
+            title = "Dummy";
+            charger = "dummy-charger";
+          } ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("evcc.service")
+    machine.wait_for_open_port(7070)
+
+    with subtest("Check package version propagates into frontend"):
+        machine.fail(
+            "curl --fail http://localhost:7070 | grep '0.0.1-alpha'"
+        )
+        machine.succeed(
+            "curl --fail http://localhost:7070 | grep '${pkgs.evcc.version}'"
+        )
+
+    with subtest("Check journal for errors"):
+        _, output = machine.execute("journalctl -o cat -u evcc.service")
+        assert "FATAL" not in output
+
+    with subtest("Check systemd hardening"):
+        _, output = machine.execute("systemd-analyze security evcc.service | grep -v '✓'")
+        machine.log(output)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fail2ban.nix b/nixpkgs/nixos/tests/fail2ban.nix
new file mode 100644
index 000000000000..c3708575b702
--- /dev/null
+++ b/nixpkgs/nixos/tests/fail2ban.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "fail2ban";
+
+  nodes.machine = _: {
+    services.fail2ban = {
+      enable = true;
+      bantime-increment.enable = true;
+    };
+
+    services.openssh.enable = true;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    machine.wait_for_unit("fail2ban")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fakeroute.nix b/nixpkgs/nixos/tests/fakeroute.nix
new file mode 100644
index 000000000000..37b174524ab8
--- /dev/null
+++ b/nixpkgs/nixos/tests/fakeroute.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ lib, pkgs, ...} : {
+  name = "fakeroute";
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    services.fakeroute.enable = true;
+    services.fakeroute.route =
+      [ "216.102.187.130" "4.0.1.122"
+        "198.116.142.34" "63.199.8.242"
+      ];
+    environment.systemPackages = [ pkgs.traceroute ];
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("fakeroute.service")
+      machine.succeed("traceroute 127.0.0.1 | grep -q 216.102.187.130")
+    '';
+})
+
diff --git a/nixpkgs/nixos/tests/fancontrol.nix b/nixpkgs/nixos/tests/fancontrol.nix
new file mode 100644
index 000000000000..ecb936097446
--- /dev/null
+++ b/nixpkgs/nixos/tests/fancontrol.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, ... } : {
+  name = "fancontrol";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ evils ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    hardware.fancontrol.enable = true;
+    hardware.fancontrol.config = ''
+      INTERVAL=42
+      DEVPATH=hwmon1=devices/platform/dummy
+      DEVNAME=hwmon1=dummy
+      FCTEMPS=hwmon1/device/pwm1=hwmon1/device/temp1_input
+      FCFANS=hwmon1/device/pwm1=hwmon1/device/fan1_input
+      MINTEMP=hwmon1/device/pwm1=25
+      MAXTEMP=hwmon1/device/pwm1=65
+      MINSTART=hwmon1/device/pwm1=150
+      MINSTOP=hwmon1/device/pwm1=0
+    '';
+    };
+
+  # This configuration cannot be valid for the test VM, so it's expected to get an 'outdated' error.
+  testScript = ''
+    start_all()
+    # can't wait for unit fancontrol.service because it doesn't become active due to invalid config
+    # fancontrol.service is WantedBy multi-user.target
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed(
+        "journalctl -eu fancontrol | tee /dev/stderr | grep 'Configuration appears to be outdated'"
+    )
+    machine.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fanout.nix b/nixpkgs/nixos/tests/fanout.nix
new file mode 100644
index 000000000000..c36d34dcce0b
--- /dev/null
+++ b/nixpkgs/nixos/tests/fanout.nix
@@ -0,0 +1,30 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+}:
+import ./make-test-python.nix ({lib, pkgs, ...}: {
+  name = "fanout";
+  meta.maintainers = [ lib.maintainers.therishidesai ];
+
+  nodes = let
+    cfg = { ... }: {
+      services.fanout = {
+        enable = true;
+        fanoutDevices = 2;
+        bufferSize = 8192;
+      };
+    };
+  in {
+    machine = cfg;
+  };
+
+  testScript = ''
+    start_all()
+
+    # mDNS.
+    machine.wait_for_unit("multi-user.target")
+
+    machine.succeed("test -c /dev/fanout0")
+    machine.succeed("test -c /dev/fanout1")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fastnetmon-advanced.nix b/nixpkgs/nixos/tests/fastnetmon-advanced.nix
new file mode 100644
index 000000000000..b2d2713a9211
--- /dev/null
+++ b/nixpkgs/nixos/tests/fastnetmon-advanced.nix
@@ -0,0 +1,65 @@
+{ pkgs, lib, ... }:
+
+{
+  name = "fastnetmon-advanced";
+  meta.maintainers = lib.teams.wdz.members;
+
+  nodes = {
+    bird = { ... }: {
+      networking.firewall.allowedTCPPorts = [ 179 ];
+      services.bird2 = {
+        enable = true;
+        config = ''
+          router id 192.168.1.1;
+
+          protocol bgp fnm {
+            local 192.168.1.1 as 64513;
+            neighbor 192.168.1.2 as 64514;
+            multihop;
+            ipv4 {
+              import all;
+              export none;
+            };
+          }
+        '';
+      };
+    };
+    fnm = { ... }: {
+      networking.firewall.allowedTCPPorts = [ 179 ];
+      services.fastnetmon-advanced = {
+        enable = true;
+        settings = {
+          networks_list = [ "172.23.42.0/24" ];
+          gobgp = true;
+          gobgp_flow_spec_announces = true;
+        };
+        bgpPeers = {
+          bird = {
+            local_asn = 64514;
+            remote_asn = 64513;
+            local_address = "192.168.1.2";
+            remote_address = "192.168.1.1";
+
+            description = "Bird";
+            ipv4_unicast = true;
+            multihop = true;
+            active = true;
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+    fnm.wait_for_unit("fastnetmon.service")
+    bird.wait_for_unit("bird2.service")
+
+    fnm.wait_until_succeeds('journalctl -eu fastnetmon.service | grep "BGP daemon restarted correctly"')
+    fnm.wait_until_succeeds("journalctl -eu gobgp.service | grep BGP_FSM_OPENCONFIRM")
+    bird.wait_until_succeeds("birdc show protocol fnm | grep Estab")
+    fnm.wait_until_succeeds('journalctl -eu fastnetmon.service | grep "API server listening"')
+    fnm.succeed("fcli set blackhole 172.23.42.123")
+    bird.succeed("birdc show route | grep 172.23.42.123")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/fcitx5/default.nix b/nixpkgs/nixos/tests/fcitx5/default.nix
new file mode 100644
index 000000000000..c113f2e2c052
--- /dev/null
+++ b/nixpkgs/nixos/tests/fcitx5/default.nix
@@ -0,0 +1,165 @@
+import ../make-test-python.nix ({ lib, ... }:
+rec {
+  name = "fcitx5";
+  meta.maintainers = with lib.maintainers; [ nevivurn ];
+
+  nodes.machine = { pkgs, ... }:
+  {
+    imports = [
+      ../common/user-account.nix
+    ];
+
+    environment.systemPackages = [
+      # To avoid clashing with xfce4-terminal
+      pkgs.alacritty
+    ];
+
+    services.xserver = {
+      enable = true;
+
+      displayManager = {
+        lightdm.enable = true;
+        autoLogin = {
+          enable = true;
+          user = "alice";
+        };
+      };
+
+      desktopManager.xfce.enable = true;
+    };
+
+    i18n.inputMethod = {
+      enabled = "fcitx5";
+      fcitx5.addons = [
+        pkgs.fcitx5-chinese-addons
+        pkgs.fcitx5-hangul
+        pkgs.fcitx5-m17n
+        pkgs.fcitx5-mozc
+      ];
+      fcitx5.settings = {
+        globalOptions = {
+          "Hotkey"."EnumerateSkipFirst" = "False";
+          "Hotkey/TriggerKeys"."0" = "Control+space";
+          "Hotkey/EnumerateForwardKeys"."0" = "Alt+Shift_L";
+          "Hotkey/EnumerateBackwardKeys"."0" = "Alt+Shift_R";
+        };
+        inputMethod = {
+          "GroupOrder" = {
+            "0" = "NixOS_test";
+          };
+          "Groups/0" = {
+            "Default Layout" = "us";
+            "DefaultIM" = "wbx";
+            "Name" = "NixOS_test";
+          };
+          "Groups/0/Items/0" = {
+            "Name" = "keyboard-us";
+          };
+          "Groups/0/Items/1" = {
+            "Layout" = "us";
+            "Name" = "wbx";
+          };
+          "Groups/0/Items/2" = {
+            "Layout" = "us";
+            "Name" = "hangul";
+          };
+          "Groups/0/Items/3" = {
+            "Layout" = "us";
+            "Name" = "m17n_sa_harvard-kyoto";
+          };
+          "Groups/0/Items/4" = {
+            "Layout" = "us";
+            "Name" = "mozc";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      xauth         = "${user.home}/.Xauthority";
+    in
+      ''
+            start_all()
+
+            machine.wait_for_x()
+            machine.wait_for_file("${xauth}")
+            machine.succeed("xauth merge ${xauth}")
+            machine.sleep(5)
+
+            machine.succeed("su - ${user.name} -c 'kill $(pgrep fcitx5)'")
+            machine.sleep(1)
+
+            machine.succeed("su - ${user.name} -c 'alacritty >&2 &'")
+            machine.succeed("su - ${user.name} -c 'fcitx5 >&2 &'")
+            machine.sleep(10)
+
+            ### Type on terminal
+            machine.send_chars("echo ")
+            machine.sleep(1)
+
+            ### Start fcitx Unicode input
+            machine.send_key("ctrl-alt-shift-u")
+            machine.sleep(1)
+
+            ### Search for smiling face
+            machine.send_chars("smil")
+            machine.sleep(1)
+
+            ### Navigate to the second one
+            machine.send_key("tab")
+            machine.sleep(1)
+
+            ### Choose it
+            machine.send_key("\n")
+            machine.sleep(1)
+
+            ### Start fcitx language input
+            machine.send_key("ctrl-spc")
+            machine.sleep(1)
+
+            ### Default wubi, enter 一下
+            machine.send_chars("gggh ")
+            machine.sleep(1)
+
+            ### Switch to Hangul
+            machine.send_key("alt-shift")
+            machine.sleep(1)
+
+            ### Enter 한
+            machine.send_chars("gks")
+            machine.sleep(1)
+
+            ### Switch to Harvard Kyoto
+            machine.send_key("alt-shift")
+            machine.sleep(1)
+
+            ### Enter क
+            machine.send_chars("ka")
+            machine.sleep(1)
+
+            ### Switch to Mozc
+            machine.send_key("alt-shift")
+            machine.sleep(1)
+
+            ### Enter ã‹
+            machine.send_chars("ka\n")
+            machine.sleep(1)
+
+            ### Turn off Fcitx
+            machine.send_key("ctrl-spc")
+            machine.sleep(1)
+
+            ### Redirect typed characters to a file
+            machine.send_chars(" > fcitx_test.out\n")
+            machine.sleep(1)
+            machine.screenshot("terminal_chars")
+
+            ### Verify that file contents are as expected
+            file_content = machine.succeed("cat ${user.home}/fcitx_test.out")
+            assert file_content == "☺一下한कã‹\n"
+      ''
+  ;
+})
diff --git a/nixpkgs/nixos/tests/fenics.nix b/nixpkgs/nixos/tests/fenics.nix
new file mode 100644
index 000000000000..1d182cfc4499
--- /dev/null
+++ b/nixpkgs/nixos/tests/fenics.nix
@@ -0,0 +1,49 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  fenicsScript = pkgs.writeScript "poisson.py" ''
+    #!/usr/bin/env python
+    from dolfin import *
+
+    mesh = UnitSquareMesh(4, 4)
+    V = FunctionSpace(mesh, "Lagrange", 1)
+
+    def boundary(x):
+        return x[0] < DOLFIN_EPS or x[0] > 1.0 - DOLFIN_EPS
+
+    u0 = Constant(0.0)
+    bc = DirichletBC(V, u0, boundary)
+
+    u = TrialFunction(V)
+    v = TestFunction(V)
+    f = Expression("10*exp(-(pow(x[0] - 0.5, 2) + pow(x[1] - 0.5, 2)) / 0.02)", degree=2)
+    g = Expression("sin(5*x[0])", degree=2)
+    a = inner(grad(u), grad(v))*dx
+    L = f*v*dx + g*v*ds
+
+    u = Function(V)
+    solve(a == L, u, bc)
+    print(u)
+  '';
+in
+{
+  name = "fenics";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ knedlsepp ];
+  };
+
+  nodes = {
+    fenicsnode = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [
+        gcc
+        (python3.withPackages (ps: with ps; [ fenics ]))
+      ];
+    };
+  };
+  testScript =
+    { nodes, ... }:
+    ''
+      start_all()
+      fenicsnode.succeed("${fenicsScript}")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/ferm.nix b/nixpkgs/nixos/tests/ferm.nix
new file mode 100644
index 000000000000..87c67ac62347
--- /dev/null
+++ b/nixpkgs/nixos/tests/ferm.nix
@@ -0,0 +1,77 @@
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "ferm";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mic92 ];
+  };
+
+  nodes =
+    { client =
+        { pkgs, ... }:
+        with pkgs.lib;
+        {
+          networking = {
+            dhcpcd.enable = false;
+            interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::2"; prefixLength = 64; } ];
+            interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          };
+      };
+      server =
+        { pkgs, ... }:
+        with pkgs.lib;
+        {
+          networking = {
+            dhcpcd.enable = false;
+            useNetworkd = true;
+            useDHCP = false;
+            interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::1"; prefixLength = 64; } ];
+            interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          };
+
+          services = {
+            ferm.enable = true;
+            ferm.config = ''
+              domain (ip ip6) table filter chain INPUT {
+                interface lo ACCEPT;
+                proto tcp dport 8080 REJECT reject-with tcp-reset;
+              }
+            '';
+            nginx.enable = true;
+            nginx.httpConfig = ''
+              server {
+                listen 80;
+                listen [::]:80;
+                listen 8080;
+                listen [::]:8080;
+
+                location /status { stub_status on; }
+              }
+            '';
+          };
+        };
+    };
+
+  testScript =
+    ''
+      start_all()
+
+      client.systemctl("start network-online.target")
+      server.systemctl("start network-online.target")
+      client.wait_for_unit("network-online.target")
+      server.wait_for_unit("network-online.target")
+      server.wait_for_unit("ferm.service")
+      server.wait_for_unit("nginx.service")
+      server.wait_until_succeeds("ss -ntl | grep -q 80")
+
+      with subtest("port 80 is allowed"):
+          client.succeed("curl --fail -g http://192.168.1.1:80/status")
+          client.succeed("curl --fail -g http://[fd00::1]:80/status")
+
+      with subtest("port 8080 is not allowed"):
+          server.succeed("curl --fail -g http://192.168.1.1:8080/status")
+          server.succeed("curl --fail -g http://[fd00::1]:8080/status")
+
+          client.fail("curl --fail -g http://192.168.1.1:8080/status")
+          client.fail("curl --fail -g http://[fd00::1]:8080/status")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/ferretdb.nix b/nixpkgs/nixos/tests/ferretdb.nix
new file mode 100644
index 000000000000..7251198af77d
--- /dev/null
+++ b/nixpkgs/nixos/tests/ferretdb.nix
@@ -0,0 +1,64 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; }
+, ...
+}:
+let
+  lib = pkgs.lib;
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("ferretdb.service")
+    machine.wait_for_open_port(27017)
+    machine.succeed("mongosh --eval 'use myNewDatabase;' --eval 'db.myCollection.insertOne( { x: 1 } );'")
+  '';
+in
+with import ../lib/testing-python.nix { inherit system; };
+{
+
+  postgresql = makeTest
+    {
+      inherit testScript;
+      name = "ferretdb-postgresql";
+      meta.maintainers = with lib.maintainers; [ julienmalka ];
+
+      nodes.machine =
+        { pkgs, ... }:
+        {
+          services.ferretdb = {
+            enable = true;
+            settings.FERRETDB_HANDLER = "pg";
+            settings.FERRETDB_POSTGRESQL_URL = "postgres://ferretdb@localhost/ferretdb?host=/run/postgresql";
+          };
+
+          systemd.services.ferretdb.serviceConfig = {
+            Requires = "postgresql.service";
+            After = "postgresql.service";
+          };
+
+          services.postgresql = {
+            enable = true;
+            ensureDatabases = [ "ferretdb" ];
+            ensureUsers = [{
+              name = "ferretdb";
+              ensureDBOwnership = true;
+            }];
+          };
+
+          environment.systemPackages = with pkgs; [ mongosh ];
+        };
+    };
+
+  sqlite = makeTest
+    {
+      inherit testScript;
+      name = "ferretdb-sqlite";
+      meta.maintainers = with lib.maintainers; [ julienmalka ];
+
+      nodes.machine =
+        { pkgs, ... }:
+        {
+          services.ferretdb.enable = true;
+
+          environment.systemPackages = with pkgs; [ mongosh ];
+        };
+    };
+}
diff --git a/nixpkgs/nixos/tests/filesystems-overlayfs.nix b/nixpkgs/nixos/tests/filesystems-overlayfs.nix
new file mode 100644
index 000000000000..d7cbf640abe4
--- /dev/null
+++ b/nixpkgs/nixos/tests/filesystems-overlayfs.nix
@@ -0,0 +1,89 @@
+{ lib, pkgs, ... }:
+
+let
+  initrdLowerdir = pkgs.runCommand "initrd-lowerdir" { } ''
+    mkdir -p $out
+    echo "initrd" > $out/initrd.txt
+  '';
+  initrdLowerdir2 = pkgs.runCommand "initrd-lowerdir-2" { } ''
+    mkdir -p $out
+    echo "initrd2" > $out/initrd2.txt
+  '';
+  userspaceLowerdir = pkgs.runCommand "userspace-lowerdir" { } ''
+    mkdir -p $out
+    echo "userspace" > $out/userspace.txt
+  '';
+  userspaceLowerdir2 = pkgs.runCommand "userspace-lowerdir-2" { } ''
+    mkdir -p $out
+    echo "userspace2" > $out/userspace2.txt
+  '';
+in
+{
+
+  name = "writable-overlays";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { config, pkgs, ... }: {
+    boot.initrd.systemd.enable = true;
+    boot.initrd.availableKernelModules = [ "overlay" ];
+
+    virtualisation.fileSystems = {
+      "/initrd-overlay" = {
+        overlay = {
+          lowerdir = [ initrdLowerdir ];
+          upperdir = "/.rw-initrd-overlay/upper";
+          workdir = "/.rw-initrd-overlay/work";
+        };
+        neededForBoot = true;
+      };
+      "/userspace-overlay" = {
+        overlay = {
+          lowerdir = [ userspaceLowerdir ];
+          upperdir = "/.rw-userspace-overlay/upper";
+          workdir = "/.rw-userspace-overlay/work";
+        };
+      };
+      "/ro-initrd-overlay" = {
+        overlay.lowerdir = [
+          initrdLowerdir
+          initrdLowerdir2
+        ];
+        neededForBoot = true;
+      };
+      "/ro-userspace-overlay" = {
+        overlay.lowerdir = [
+          userspaceLowerdir
+          userspaceLowerdir2
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("default.target")
+
+    with subtest("Initrd overlay"):
+      machine.wait_for_file("/initrd-overlay/initrd.txt", 5)
+      machine.succeed("touch /initrd-overlay/writable.txt")
+      machine.succeed("findmnt --kernel --types overlay /initrd-overlay")
+
+    with subtest("Userspace overlay"):
+      machine.wait_for_file("/userspace-overlay/userspace.txt", 5)
+      machine.succeed("touch /userspace-overlay/writable.txt")
+      machine.succeed("findmnt --kernel --types overlay /userspace-overlay")
+
+    with subtest("Read only initrd overlay"):
+      machine.wait_for_file("/ro-initrd-overlay/initrd.txt", 5)
+      machine.wait_for_file("/ro-initrd-overlay/initrd2.txt", 5)
+      machine.fail("touch /ro-initrd-overlay/not-writable.txt")
+      machine.succeed("findmnt --kernel --types overlay /ro-initrd-overlay")
+
+    with subtest("Read only userspace overlay"):
+      machine.wait_for_file("/ro-userspace-overlay/userspace.txt", 5)
+      machine.wait_for_file("/ro-userspace-overlay/userspace2.txt", 5)
+      machine.fail("touch /ro-userspace-overlay/not-writable.txt")
+      machine.succeed("findmnt --kernel --types overlay /ro-userspace-overlay")
+  '';
+
+}
diff --git a/nixpkgs/nixos/tests/firefox.nix b/nixpkgs/nixos/tests/firefox.nix
new file mode 100644
index 000000000000..fbea95dc7523
--- /dev/null
+++ b/nixpkgs/nixos/tests/firefox.nix
@@ -0,0 +1,123 @@
+import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }:
+{
+  name = firefoxPackage.pname;
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco shlevy ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      environment.systemPackages = [ pkgs.xdotool ];
+
+      programs.firefox = {
+        enable = true;
+        preferences."media.autoplay.default" = 0;
+        package = firefoxPackage;
+      };
+
+      # Create a virtual sound device, with mixing
+      # and all, for recording audio.
+      boot.kernelModules = [ "snd-aloop" ];
+      sound.enable = true;
+      sound.extraConfig = ''
+        pcm.!default {
+          type plug
+          slave.pcm pcm.dmixer
+        }
+        pcm.dmixer {
+          type dmix
+          ipc_key 1
+          slave {
+            pcm "hw:Loopback,0,0"
+            rate 48000
+            periods 128
+            period_time 0
+            period_size 1024
+            buffer_size 8192
+          }
+        }
+        pcm.recorder {
+          type hw
+          card "Loopback"
+          device 1
+          subdevice 0
+        }
+      '';
+
+      systemd.services.audio-recorder = {
+        description = "Record NixOS test audio to /tmp/record.wav";
+        script = "${pkgs.alsa-utils}/bin/arecord -D recorder -f S16_LE -r48000 /tmp/record.wav";
+      };
+
+    };
+
+  testScript = let
+    exe = firefoxPackage.unwrapped.binaryName;
+  in ''
+      from contextlib import contextmanager
+
+
+      @contextmanager
+      def record_audio(machine: Machine):
+          """
+          Perform actions while recording the
+          machine audio output.
+          """
+          machine.systemctl("start audio-recorder")
+          yield
+          machine.systemctl("stop audio-recorder")
+
+
+      def wait_for_sound(machine: Machine):
+          """
+          Wait until any sound has been emitted.
+          """
+          machine.wait_for_file("/tmp/record.wav")
+          while True:
+              # Get at most 2M of the recording
+              machine.execute("tail -c 2M /tmp/record.wav > /tmp/last")
+              # Get the exact size
+              size = int(machine.succeed("stat -c '%s' /tmp/last").strip())
+              # Compare it against /dev/zero using `cmp` (skipping 50B of WAVE header).
+              # If some non-NULL bytes are found it returns 1.
+              status, output = machine.execute(
+                  f"cmp -i 50 -n {size - 50} /tmp/last /dev/zero 2>&1"
+              )
+              if status == 1:
+                  break
+              machine.sleep(2)
+
+
+      machine.wait_for_x()
+
+      with subtest("Wait until Firefox has finished loading the Valgrind docs page"):
+          machine.execute(
+              "xterm -e '${exe} file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' >&2 &"
+          )
+          machine.wait_for_window("Valgrind")
+          machine.sleep(40)
+
+      with subtest("Check whether Firefox can play sound"):
+          with record_audio(machine):
+              machine.succeed(
+                  "${exe} file://${pkgs.sound-theme-freedesktop}/share/sounds/freedesktop/stereo/phone-incoming-call.oga >&2 &"
+              )
+              wait_for_sound(machine)
+          machine.copy_from_vm("/tmp/record.wav")
+
+      with subtest("Close sound test tab"):
+          machine.execute("xdotool key ctrl+w")
+
+      with subtest("Close default browser prompt"):
+          machine.execute("xdotool key space")
+
+      with subtest("Wait until Firefox draws the developer tool panel"):
+          machine.sleep(10)
+          machine.succeed("xwininfo -root -tree | grep Valgrind")
+          machine.screenshot("screen")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/firejail.nix b/nixpkgs/nixos/tests/firejail.nix
new file mode 100644
index 000000000000..6c42c37b2813
--- /dev/null
+++ b/nixpkgs/nixos/tests/firejail.nix
@@ -0,0 +1,91 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "firejail";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ sgo ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+
+    programs.firejail = {
+      enable = true;
+      wrappedBinaries = {
+        bash-jailed  = "${pkgs.bash}/bin/bash";
+        bash-jailed2  = {
+          executable = "${pkgs.bash}/bin/bash";
+          extraArgs = [ "--private=~/firejail-home" ];
+        };
+      };
+    };
+
+    systemd.services.setupFirejailTest = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "multi-user.target" ];
+
+      environment = {
+        HOME = "/home/alice";
+      };
+
+      unitConfig = {
+        type = "oneshot";
+        RemainAfterExit = true;
+        user = "alice";
+      };
+
+      script = ''
+        cd $HOME
+
+        mkdir .password-store && echo s3cret > .password-store/secret
+        mkdir my-secrets && echo s3cret > my-secrets/secret
+
+        echo publ1c > public
+
+        mkdir -p .config/firejail
+        echo 'blacklist ''${HOME}/my-secrets' > .config/firejail/globals.local
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+
+    # Test path acl with wrapper
+    machine.succeed("sudo -u alice bash-jailed -c 'cat ~/public' | grep -q publ1c")
+    machine.fail(
+        "sudo -u alice bash-jailed -c 'cat ~/.password-store/secret' | grep -q s3cret"
+    )
+    machine.fail("sudo -u alice bash-jailed -c 'cat ~/my-secrets/secret' | grep -q s3cret")
+
+    # Test extraArgs
+    machine.succeed("sudo -u alice mkdir /home/alice/firejail-home")
+    machine.succeed("sudo -u alice bash-jailed2 -c 'echo test > /home/alice/foo'")
+    machine.fail("sudo -u alice cat /home/alice/foo")
+    machine.succeed("sudo -u alice cat /home/alice/firejail-home/foo | grep test")
+
+    # Test path acl with firejail executable
+    machine.succeed("sudo -u alice firejail -- bash -c 'cat ~/public' | grep -q publ1c")
+    machine.fail(
+        "sudo -u alice firejail -- bash -c 'cat ~/.password-store/secret' | grep -q s3cret"
+    )
+    machine.fail(
+        "sudo -u alice firejail -- bash -c 'cat ~/my-secrets/secret' | grep -q s3cret"
+    )
+
+    # Disabling profiles
+    machine.succeed(
+        "sudo -u alice bash -c 'firejail --noprofile -- cat ~/.password-store/secret' | grep -q s3cret"
+    )
+
+    # CVE-2020-17367
+    machine.fail(
+        "sudo -u alice firejail --private-tmp id --output=/tmp/vuln1 && cat /tmp/vuln1"
+    )
+
+    # CVE-2020-17368
+    machine.fail(
+        "sudo -u alice firejail --private-tmp --output=/tmp/foo 'bash -c $(id>/tmp/vuln2;echo id)' && cat /tmp/vuln2"
+    )
+  '';
+})
+
diff --git a/nixpkgs/nixos/tests/firewall.nix b/nixpkgs/nixos/tests/firewall.nix
new file mode 100644
index 000000000000..dd7551f143a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/firewall.nix
@@ -0,0 +1,68 @@
+# Test the firewall module.
+
+import ./make-test-python.nix ( { pkgs, nftables, ... } : {
+  name = "firewall" + pkgs.lib.optionalString nftables "-nftables";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes =
+    { walled =
+        { ... }:
+        { networking.firewall.enable = true;
+          networking.firewall.logRefusedPackets = true;
+          networking.nftables.enable = nftables;
+          services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+        };
+
+      # Dummy configuration to check whether firewall.service will be honored
+      # during system activation. This only needs to be different to the
+      # original walled configuration so that there is a change in the service
+      # file.
+      walled2 =
+        { ... }:
+        { networking.firewall.enable = true;
+          networking.firewall.rejectPackets = true;
+          networking.nftables.enable = nftables;
+        };
+
+      attacker =
+        { ... }:
+        { services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript = { nodes, ... }: let
+    newSystem = nodes.walled2.config.system.build.toplevel;
+    unit = if nftables then "nftables" else "firewall";
+  in ''
+    start_all()
+
+    walled.wait_for_unit("${unit}")
+    walled.wait_for_unit("httpd")
+    attacker.wait_for_unit("network.target")
+
+    # Local connections should still work.
+    walled.succeed("curl -v http://localhost/ >&2")
+
+    # Connections to the firewalled machine should fail, but ping should succeed.
+    attacker.fail("curl --fail --connect-timeout 2 http://walled/ >&2")
+    attacker.succeed("ping -c 1 walled >&2")
+
+    # Outgoing connections/pings should still work.
+    walled.succeed("curl -v http://attacker/ >&2")
+    walled.succeed("ping -c 1 attacker >&2")
+
+    # If we stop the firewall, then connections should succeed.
+    walled.stop_job("${unit}")
+    attacker.succeed("curl -v http://walled/ >&2")
+
+    # Check whether activation of a new configuration reloads the firewall.
+    walled.succeed(
+        "${newSystem}/bin/switch-to-configuration test 2>&1 | grep -qF ${unit}.service"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fish.nix b/nixpkgs/nixos/tests/fish.nix
new file mode 100644
index 000000000000..3d9b13c6af70
--- /dev/null
+++ b/nixpkgs/nixos/tests/fish.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "fish";
+
+  nodes.machine =
+    { pkgs, ... }:
+
+    {
+      programs.fish.enable = true;
+      environment.systemPackages = with pkgs; [
+        coreutils
+        procps # kill collides with coreutils' to test https://github.com/NixOS/nixpkgs/issues/56432
+      ];
+    };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_file("/etc/fish/generated_completions/coreutils.fish")
+      machine.wait_for_file("/etc/fish/generated_completions/kill.fish")
+      machine.succeed(
+          "fish -ic 'echo $fish_complete_path' | grep -q '/share/fish/completions /etc/fish/generated_completions /root/.local/share/fish/generated_completions$'"
+      )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/flannel.nix b/nixpkgs/nixos/tests/flannel.nix
new file mode 100644
index 000000000000..7615732c20ca
--- /dev/null
+++ b/nixpkgs/nixos/tests/flannel.nix
@@ -0,0 +1,57 @@
+import ./make-test-python.nix ({ lib, ...} : {
+  name = "flannel";
+
+  meta = with lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = let
+    flannelConfig = { pkgs, ... } : {
+      services.flannel = {
+        enable = true;
+        backend = {
+          Type = "udp";
+          Port = 8285;
+        };
+        network = "10.1.0.0/16";
+        iface = "eth1";
+        etcd.endpoints = ["http://etcd:2379"];
+      };
+
+      networking.firewall.allowedUDPPorts = [ 8285 ];
+    };
+  in {
+    etcd = { ... }: {
+      services = {
+        etcd = {
+          enable = true;
+          listenClientUrls = ["http://0.0.0.0:2379"]; # requires ip-address for binding
+          listenPeerUrls = ["http://0.0.0.0:2380"]; # requires ip-address for binding
+          advertiseClientUrls = ["http://etcd:2379"];
+          initialAdvertisePeerUrls = ["http://etcd:2379"];
+          initialCluster = ["etcd=http://etcd:2379"];
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 2379 ];
+    };
+
+    node1 = flannelConfig;
+    node2 = flannelConfig;
+  };
+
+  testScript = ''
+    start_all()
+
+    node1.wait_for_unit("flannel.service")
+    node2.wait_for_unit("flannel.service")
+
+    node1.wait_until_succeeds("ip l show dev flannel0")
+    ip1 = node1.succeed("ip -4 addr show flannel0 | grep -oP '(?<=inet).*(?=/)'")
+    node2.wait_until_succeeds("ip l show dev flannel0")
+    ip2 = node2.succeed("ip -4 addr show flannel0 | grep -oP '(?<=inet).*(?=/)'")
+
+    node1.wait_until_succeeds(f"ping -c 1 {ip2}")
+    node2.wait_until_succeeds(f"ping -c 1 {ip1}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fluentd.nix b/nixpkgs/nixos/tests/fluentd.nix
new file mode 100644
index 000000000000..150638f246f2
--- /dev/null
+++ b/nixpkgs/nixos/tests/fluentd.nix
@@ -0,0 +1,49 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "fluentd";
+
+  nodes.machine = { pkgs, ... }: {
+    services.fluentd = {
+      enable = true;
+      config = ''
+        <source>
+          @type http
+          port 9880
+        </source>
+
+        <match **>
+          type copy
+          <store>
+            @type file
+            format json
+            path /tmp/fluentd
+            symlink_path /tmp/current-log
+          </store>
+          <store>
+            @type stdout
+          </store>
+        </match>
+      '';
+    };
+  };
+
+  testScript = let
+    testMessage = "an example log message";
+
+    payload = pkgs.writeText "test-message.json" (builtins.toJSON {
+      inherit testMessage;
+    });
+  in ''
+    machine.start()
+    machine.wait_for_unit("fluentd.service")
+    machine.wait_for_open_port(9880)
+
+    machine.succeed(
+        "curl -fsSL -X POST -H 'Content-type: application/json' -d @${payload} http://localhost:9880/test.tag"
+    )
+
+    # blocking flush
+    machine.succeed("systemctl stop fluentd")
+
+    machine.succeed("grep '${testMessage}' /tmp/current-log")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fluidd.nix b/nixpkgs/nixos/tests/fluidd.nix
new file mode 100644
index 000000000000..82a2c1e4049f
--- /dev/null
+++ b/nixpkgs/nixos/tests/fluidd.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "fluidd";
+  meta.maintainers = with lib.maintainers; [ vtuan10 ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.fluidd = {
+      enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_open_port(80)
+    machine.succeed("curl -sSfL http://localhost/ | grep 'fluidd'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/fontconfig-default-fonts.nix b/nixpkgs/nixos/tests/fontconfig-default-fonts.nix
new file mode 100644
index 000000000000..293dc43f91f3
--- /dev/null
+++ b/nixpkgs/nixos/tests/fontconfig-default-fonts.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "fontconfig-default-fonts";
+
+  meta.maintainers = with lib.maintainers; [
+    jtojnar
+  ];
+
+  nodes.machine = { config, pkgs, ... }: {
+    fonts.enableDefaultPackages = true; # Background fonts
+    fonts.packages = with pkgs; [
+      noto-fonts-color-emoji
+      cantarell-fonts
+      twitter-color-emoji
+      source-code-pro
+      gentium
+    ];
+    fonts.fontconfig.defaultFonts = {
+      serif = [ "Gentium Plus" ];
+      sansSerif = [ "Cantarell" ];
+      monospace = [ "Source Code Pro" ];
+      emoji = [ "Twitter Color Emoji" ];
+    };
+  };
+
+  testScript = ''
+    machine.succeed("fc-match serif | grep '\"Gentium Plus\"'")
+    machine.succeed("fc-match sans-serif | grep '\"Cantarell\"'")
+    machine.succeed("fc-match monospace | grep '\"Source Code Pro\"'")
+    machine.succeed("fc-match emoji | grep '\"Twitter Color Emoji\"'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/forgejo.nix b/nixpkgs/nixos/tests/forgejo.nix
new file mode 100644
index 000000000000..6acd6acb50fa
--- /dev/null
+++ b/nixpkgs/nixos/tests/forgejo.nix
@@ -0,0 +1,178 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  ## gpg --faked-system-time='20230301T010000!' --quick-generate-key snakeoil ed25519 sign
+  signingPrivateKey = ''
+    -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+    lFgEY/6jkBYJKwYBBAHaRw8BAQdADXiZRV8RJUyC9g0LH04wLMaJL9WTc+szbMi7
+    5fw4yP8AAQCl8EwGfzSLm/P6fCBfA3I9znFb3MEHGCCJhJ6VtKYyRw7ktAhzbmFr
+    ZW9pbIiUBBMWCgA8FiEE+wUM6VW/NLtAdSixTWQt6LZ4x50FAmP+o5ACGwMFCQPC
+    ZwAECwkIBwQVCgkIBRYCAwEAAh4FAheAAAoJEE1kLei2eMedFTgBAKQs1oGFZrCI
+    TZP42hmBTKxGAI1wg7VSdDEWTZxut/2JAQDGgo2sa4VHMfj0aqYGxrIwfP2B7JHO
+    GCqGCRf9O/hzBA==
+    =9Uy3
+    -----END PGP PRIVATE KEY BLOCK-----
+  '';
+  signingPrivateKeyId = "4D642DE8B678C79D";
+
+  supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
+  makeGForgejoTest = type: nameValuePair type (makeTest {
+    name = "forgejo-${type}";
+    meta.maintainers = with maintainers; [ bendlas emilylange ];
+
+    nodes = {
+      server = { config, pkgs, ... }: {
+        virtualisation.memorySize = 2047;
+        services.forgejo = {
+          enable = true;
+          database = { inherit type; };
+          settings.service.DISABLE_REGISTRATION = true;
+          settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
+          settings.actions.ENABLED = true;
+        };
+        environment.systemPackages = [ config.services.forgejo.package pkgs.gnupg pkgs.jq pkgs.file ];
+        services.openssh.enable = true;
+
+        specialisation.runner = {
+          inheritParentConfig = true;
+          configuration.services.gitea-actions-runner.instances."test" = {
+            enable = true;
+            name = "ci";
+            url = "http://localhost:3000";
+            labels = [
+              # don't require docker/podman
+              "native:host"
+            ];
+            tokenFile = "/var/lib/forgejo/runner_token";
+          };
+        };
+        specialisation.dump = {
+          inheritParentConfig = true;
+          configuration.services.forgejo.dump = {
+            enable = true;
+            type = "tar.zst";
+            file = "dump.tar.zst";
+          };
+        };
+      };
+      client1 = { config, pkgs, ... }: {
+        environment.systemPackages = [ pkgs.git ];
+      };
+      client2 = { config, pkgs, ... }: {
+        environment.systemPackages = [ pkgs.git ];
+      };
+    };
+
+    testScript = { nodes, ... }:
+      let
+        inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+        serverSystem = nodes.server.system.build.toplevel;
+        dumpFile = with nodes.server.specialisation.dump.configuration.services.forgejo.dump; "${backupDir}/${file}";
+      in
+      ''
+        import json
+        GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no"
+        REPO = "forgejo@server:test/repo"
+        PRIVK = "${snakeOilPrivateKey}"
+
+        start_all()
+
+        client1.succeed("mkdir /tmp/repo")
+        client1.succeed("mkdir -p $HOME/.ssh")
+        client1.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
+        client1.succeed("chmod 0400 $HOME/.ssh/privk")
+        client1.succeed("git -C /tmp/repo init")
+        client1.succeed("echo hello world > /tmp/repo/testfile")
+        client1.succeed("git -C /tmp/repo add .")
+        client1.succeed("git config --global user.email test@localhost")
+        client1.succeed("git config --global user.name test")
+        client1.succeed("git -C /tmp/repo commit -m 'Initial import'")
+        client1.succeed(f"git -C /tmp/repo remote add origin {REPO}")
+
+        server.wait_for_unit("forgejo.service")
+        server.wait_for_open_port(3000)
+        server.wait_for_open_port(22)
+        server.succeed("curl --fail http://localhost:3000/")
+
+        server.succeed(
+            "su -l forgejo -c 'gpg --homedir /var/lib/forgejo/data/home/.gnupg "
+            + "--import ${toString (pkgs.writeText "forgejo.key" signingPrivateKey)}'"
+        )
+
+        assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
+
+        server.succeed(
+            "curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. "
+            + "Please contact your site administrator.'"
+        )
+        server.succeed(
+            "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea admin user create "
+            + "--username test --password totallysafe --email test@localhost'"
+        )
+
+        api_token = server.succeed(
+            "curl --fail -X POST http://test:totallysafe@localhost:3000/api/v1/users/test/tokens "
+            + "-H 'Accept: application/json' -H 'Content-Type: application/json' -d "
+            + "'{\"name\":\"token\",\"scopes\":[\"all\"]}' | jq '.sha1' | xargs echo -n"
+        )
+
+        server.succeed(
+            "curl --fail -X POST http://localhost:3000/api/v1/user/repos "
+            + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
+            + f"-H 'Authorization: token {api_token}'"
+            + ' -d \'{"auto_init":false, "description":"string", "license":"mit", "name":"repo", "private":false}\'''
+        )
+
+        server.succeed(
+            "curl --fail -X POST http://localhost:3000/api/v1/user/keys "
+            + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
+            + f"-H 'Authorization: token {api_token}'"
+            + ' -d \'{"key":"${snakeOilPublicKey}","read_only":true,"title":"SSH"}\'''
+        )
+
+        client1.succeed(
+            f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git -C /tmp/repo push origin master"
+        )
+
+        client2.succeed("mkdir -p $HOME/.ssh")
+        client2.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
+        client2.succeed("chmod 0400 $HOME/.ssh/privk")
+        client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git clone {REPO}")
+        client2.succeed('test "$(cat repo/testfile | xargs echo -n)" = "hello world"')
+
+        with subtest("Testing git protocol version=2 over ssh"):
+            git_protocol = client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' GIT_TRACE2_EVENT=true git -C repo fetch |& grep negotiated-version")
+            version = json.loads(git_protocol).get("value")
+            assert version == "2", f"git did not negotiate protocol version 2, but version {version} instead."
+
+        server.wait_until_succeeds(
+            'test "$(curl http://localhost:3000/api/v1/repos/test/repo/commits '
+            + '-H "Accept: application/json" | jq length)" = "1"',
+            timeout=10
+        )
+
+        with subtest("Testing runner registration"):
+            server.succeed(
+                "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/forgejo/runner_token"
+            )
+            server.succeed("${serverSystem}/specialisation/runner/bin/switch-to-configuration test")
+            server.wait_for_unit("gitea-runner-test.service")
+            server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'")
+
+        with subtest("Testing backup service"):
+            server.succeed("${serverSystem}/specialisation/dump/bin/switch-to-configuration test")
+            server.systemctl("start forgejo-dump")
+            assert "Zstandard compressed data" in server.succeed("file ${dumpFile}")
+            server.copy_from_vm("${dumpFile}")
+      '';
+  });
+in
+
+listToAttrs (map makeGForgejoTest supportedDbTypes)
diff --git a/nixpkgs/nixos/tests/freenet.nix b/nixpkgs/nixos/tests/freenet.nix
new file mode 100644
index 000000000000..96dbb4caa129
--- /dev/null
+++ b/nixpkgs/nixos/tests/freenet.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "freenet";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ nagy ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.freenet.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("freenet.service")
+    machine.wait_for_open_port(8888)
+    machine.wait_until_succeeds("curl -sfL http://localhost:8888/ | grep Freenet")
+    machine.succeed("systemctl stop freenet")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/freeswitch.nix b/nixpkgs/nixos/tests/freeswitch.nix
new file mode 100644
index 000000000000..bfb7339ec3c0
--- /dev/null
+++ b/nixpkgs/nixos/tests/freeswitch.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "freeswitch";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ misuzu ];
+  };
+  nodes = {
+    node0 = { config, lib, ... }: {
+      networking.useDHCP = false;
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          {
+            address = "192.168.0.1";
+            prefixLength = 24;
+          }
+        ];
+      };
+      services.freeswitch = {
+        enable = true;
+        enableReload = true;
+        configTemplate = "${config.services.freeswitch.package}/share/freeswitch/conf/minimal";
+      };
+    };
+  };
+  testScript = ''
+    node0.wait_for_unit("freeswitch.service")
+    # Wait for SIP port to be open
+    node0.wait_for_open_port(5060)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/freetube.nix b/nixpkgs/nixos/tests/freetube.nix
new file mode 100644
index 000000000000..faa534938227
--- /dev/null
+++ b/nixpkgs/nixos/tests/freetube.nix
@@ -0,0 +1,43 @@
+let
+  tests = {
+    wayland = { pkgs, ... }: {
+      imports = [ ./common/wayland-cage.nix ];
+      services.cage.program = "${pkgs.freetube}/bin/freetube";
+      virtualisation.memorySize = 2047;
+      environment.variables.NIXOS_OZONE_WL = "1";
+      environment.variables.DISPLAY = "do not use";
+    };
+    xorg = { pkgs, ... }: {
+      imports = [ ./common/user-account.nix ./common/x11.nix ];
+      virtualisation.memorySize = 2047;
+      services.xserver.enable = true;
+      services.xserver.displayManager.sessionCommands = ''
+        ${pkgs.freetube}/bin/freetube
+      '';
+      test-support.displayManager.auto.user = "alice";
+    };
+  };
+
+  mkTest = name: machine:
+    import ./make-test-python.nix ({ pkgs, ... }: {
+      inherit name;
+      nodes = { "${name}" = machine; };
+      meta.maintainers = with pkgs.lib.maintainers; [ kirillrdy ];
+      # time-out on ofborg
+      meta.broken = pkgs.stdenv.isAarch64;
+      enableOCR = true;
+
+      testScript = ''
+        start_all()
+        machine.wait_for_unit('graphical.target')
+        machine.wait_for_text('Your Subscription list is currently empty')
+        machine.send_key("ctrl-r")
+        machine.wait_for_text('Your Subscription list is currently empty')
+        machine.screenshot("main.png")
+        machine.send_key("ctrl-comma")
+        machine.wait_for_text('General Settings', timeout=30)
+        machine.screenshot("preferences.png")
+      '';
+    });
+in
+builtins.mapAttrs (k: v: mkTest k v { }) tests
diff --git a/nixpkgs/nixos/tests/freshrss-http-auth.nix b/nixpkgs/nixos/tests/freshrss-http-auth.nix
new file mode 100644
index 000000000000..d0ec3da31689
--- /dev/null
+++ b/nixpkgs/nixos/tests/freshrss-http-auth.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "freshrss";
+  meta.maintainers = with lib.maintainers; [ mattchrist ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.freshrss = {
+      enable = true;
+      baseUrl = "http://localhost";
+      dataDir = "/srv/freshrss";
+      authType = "http_auth";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(80)
+    response = machine.succeed("curl -vvv -s -H 'Host: freshrss' -H 'Remote-User: testuser' http://127.0.0.1:80/i/")
+    assert 'Account: testuser' in response, "http_auth method didn't work."
+  '';
+})
diff --git a/nixpkgs/nixos/tests/freshrss-pgsql.nix b/nixpkgs/nixos/tests/freshrss-pgsql.nix
new file mode 100644
index 000000000000..c685f4a8159b
--- /dev/null
+++ b/nixpkgs/nixos/tests/freshrss-pgsql.nix
@@ -0,0 +1,46 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "freshrss";
+  meta.maintainers = with lib.maintainers; [ etu stunkymonkey ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.freshrss = {
+      enable = true;
+      baseUrl = "http://localhost";
+      passwordFile = pkgs.writeText "password" "secret";
+      dataDir = "/srv/freshrss";
+      database = {
+        type = "pgsql";
+        port = 5432;
+        user = "freshrss";
+        passFile = pkgs.writeText "db-password" "db-secret";
+      };
+    };
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "freshrss" ];
+      ensureUsers = [
+        {
+          name = "freshrss";
+          ensureDBOwnership = true;
+        }
+      ];
+      initialScript = pkgs.writeText "postgresql-password" ''
+        CREATE ROLE freshrss WITH LOGIN PASSWORD 'db-secret' CREATEDB;
+      '';
+    };
+
+    systemd.services."freshrss-config" = {
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(5432)
+    machine.wait_for_open_port(80)
+    response = machine.succeed("curl -vvv -s -H 'Host: freshrss' http://127.0.0.1:80/i/")
+    assert '<title>Login · FreshRSS</title>' in response, "Login page didn't load successfully"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/freshrss-sqlite.nix b/nixpkgs/nixos/tests/freshrss-sqlite.nix
new file mode 100644
index 000000000000..b821c98a7e7a
--- /dev/null
+++ b/nixpkgs/nixos/tests/freshrss-sqlite.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "freshrss";
+  meta.maintainers = with lib.maintainers; [ etu stunkymonkey ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.freshrss = {
+      enable = true;
+      baseUrl = "http://localhost";
+      passwordFile = pkgs.writeText "password" "secret";
+      dataDir = "/srv/freshrss";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(80)
+    response = machine.succeed("curl -vvv -s -H 'Host: freshrss' http://127.0.0.1:80/i/")
+    assert '<title>Login · FreshRSS</title>' in response, "Login page didn't load successfully"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/frigate.nix b/nixpkgs/nixos/tests/frigate.nix
new file mode 100644
index 000000000000..03bd2b89611d
--- /dev/null
+++ b/nixpkgs/nixos/tests/frigate.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+  name = "frigate";
+  meta.maintainers = with lib.maintainers; [ hexa ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.frigate = {
+        enable = true;
+
+        hostname = "localhost";
+
+        settings = {
+          mqtt.enabled = false;
+
+          cameras.test = {
+            ffmpeg = {
+              input_args = "-fflags nobuffer -strict experimental -fflags +genpts+discardcorrupt -r 10 -use_wallclock_as_timestamps 1";
+              inputs = [ {
+                path = "http://127.0.0.1:8080";
+                roles = [
+                  "record"
+                ];
+              } ];
+            };
+          };
+
+          record.enabled = true;
+        };
+      };
+
+      systemd.services.video-stream = {
+        description = "Start a test stream that frigate can capture";
+        before = [
+          "frigate.service"
+        ];
+        wantedBy = [
+          "multi-user.target"
+        ];
+        serviceConfig = {
+          DynamicUser = true;
+          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -f mpegts -listen 1 http://0.0.0.0:8080";
+          Restart = "always";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("frigate.service")
+
+    # Frigate startup
+    machine.wait_for_open_port(5001)
+
+    # nginx startup
+    machine.wait_for_open_port(80)
+
+    machine.succeed("curl http://localhost")
+
+    machine.wait_for_file("/var/cache/frigate/test@*.mp4")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/frp.nix b/nixpkgs/nixos/tests/frp.nix
new file mode 100644
index 000000000000..1f57c031a53a
--- /dev/null
+++ b/nixpkgs/nixos/tests/frp.nix
@@ -0,0 +1,85 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "frp";
+  meta.maintainers = with lib.maintainers; [ zaldnoay janik ];
+  nodes = {
+    frps = {
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.1/24";
+      };
+
+      services.frp = {
+        enable = true;
+        role = "server";
+        settings = {
+          bindPort = 7000;
+          vhostHTTPPort = 80;
+        };
+      };
+    };
+
+
+    frpc = {
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.2/24";
+      };
+
+      services.httpd = {
+        enable = true;
+        adminAddr = "admin@example.com";
+        virtualHosts."test-appication" =
+        let
+          testdir = pkgs.writeTextDir "web/index.php" "<?php phpinfo();";
+        in
+        {
+          documentRoot = "${testdir}/web";
+          locations."/" = {
+            index = "index.php index.html";
+          };
+        };
+        phpPackage = pkgs.php81;
+        enablePHP = true;
+      };
+
+      services.frp = {
+        enable = true;
+        role = "client";
+        settings = {
+          serverAddr = "10.0.0.1";
+          serverPort = 7000;
+          proxies = [
+            {
+              name = "web";
+              type = "http";
+              localPort = 80;
+              customDomains = [ "10.0.0.1" ];
+            }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    frps.wait_for_unit("frp.service")
+    frps.wait_for_open_port(80)
+    frpc.wait_for_unit("frp.service")
+    response = frpc.succeed("curl -fvvv -s http://127.0.0.1/")
+    assert "PHP Version ${pkgs.php81.version}" in response, "PHP version not detected"
+    response = frpc.succeed("curl -fvvv -s http://10.0.0.1/")
+    assert "PHP Version ${pkgs.php81.version}" in response, "PHP version not detected"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/frr.nix b/nixpkgs/nixos/tests/frr.nix
new file mode 100644
index 000000000000..0d1a6a694a82
--- /dev/null
+++ b/nixpkgs/nixos/tests/frr.nix
@@ -0,0 +1,104 @@
+# This test runs FRR and checks if OSPF routing works.
+#
+# Network topology:
+#   [ client ]--net1--[ router1 ]--net2--[ router2 ]--net3--[ server ]
+#
+# All interfaces are in OSPF Area 0.
+
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+
+    ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ipv4.addresses).address;
+
+    ospfConf1 = ''
+      router ospf
+        network 192.168.0.0/16 area 0
+    '';
+
+    ospfConf2 = ''
+      interface eth2
+        ip ospf hello-interval 1
+        ip ospf dead-interval 5
+      !
+      router ospf
+        network 192.168.0.0/16 area 0
+    '';
+
+  in
+    {
+      name = "frr";
+
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ ];
+      };
+
+      nodes = {
+
+        client =
+          { nodes, ... }:
+          {
+            virtualisation.vlans = [ 1 ];
+            networking.defaultGateway = ifAddr nodes.router1 "eth1";
+          };
+
+        router1 =
+          { ... }:
+          {
+            virtualisation.vlans = [ 1 2 ];
+            boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+            networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospfigp -j ACCEPT";
+            services.frr.ospf = {
+              enable = true;
+              config = ospfConf1;
+            };
+
+            specialisation.ospf.configuration = {
+              services.frr.ospf.config = ospfConf2;
+            };
+          };
+
+        router2 =
+          { ... }:
+          {
+            virtualisation.vlans = [ 3 2 ];
+            boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+            networking.firewall.extraCommands = "iptables -A nixos-fw -i eth2 -p ospfigp -j ACCEPT";
+            services.frr.ospf = {
+              enable = true;
+              config = ospfConf2;
+            };
+          };
+
+        server =
+          { nodes, ... }:
+          {
+            virtualisation.vlans = [ 3 ];
+            networking.defaultGateway = ifAddr nodes.router2 "eth1";
+          };
+      };
+
+      testScript =
+        { nodes, ... }:
+        ''
+          start_all()
+
+          # Wait for the networking to start on all machines
+          for machine in client, router1, router2, server:
+              machine.wait_for_unit("network.target")
+
+          with subtest("Wait for Zebra and OSPFD"):
+              for gw in router1, router2:
+                  gw.wait_for_unit("zebra")
+                  gw.wait_for_unit("ospfd")
+
+          router1.succeed("${nodes.router1.config.system.build.toplevel}/specialisation/ospf/bin/switch-to-configuration test >&2")
+
+          with subtest("Wait for OSPF to form adjacencies"):
+              for gw in router1, router2:
+                  gw.wait_until_succeeds("vtysh -c 'show ip ospf neighbor' | grep Full")
+                  gw.wait_until_succeeds("vtysh -c 'show ip route' | grep '^O>'")
+
+          with subtest("Test ICMP"):
+              client.wait_until_succeeds("ping -c 3 server >&2")
+        '';
+    })
diff --git a/nixpkgs/nixos/tests/fsck.nix b/nixpkgs/nixos/tests/fsck.nix
new file mode 100644
index 000000000000..31ed8bdf78c0
--- /dev/null
+++ b/nixpkgs/nixos/tests/fsck.nix
@@ -0,0 +1,45 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, systemdStage1 ? false
+}:
+
+import ./make-test-python.nix {
+  name = "fsck";
+
+  nodes.machine = { lib, ... }: {
+    virtualisation.emptyDiskImages = [ 1 ];
+
+    virtualisation.fileSystems = {
+      "/mnt" = {
+        device = "/dev/vdb";
+        fsType = "ext4";
+        autoFormat = true;
+      };
+    };
+
+    boot.initrd.systemd.enable = systemdStage1;
+  };
+
+  testScript =  { nodes, ...}:
+  let
+    rootDevice = nodes.machine.virtualisation.rootDevice;
+  in
+  ''
+    machine.wait_for_unit("default.target")
+
+    with subtest("root fs is fsckd"):
+        machine.succeed("journalctl -b | grep '${if systemdStage1
+          then "fsck.*${builtins.baseNameOf rootDevice}.*clean"
+          else "fsck.ext4.*${rootDevice}"}'")
+
+    with subtest("mnt fs is fsckd"):
+        machine.succeed("journalctl -b | grep 'fsck.*vdb.*clean'")
+        machine.succeed(
+            "grep 'Requires=systemd-fsck@dev-vdb.service' /run/systemd/generator/mnt.mount"
+        )
+        machine.succeed(
+            "grep 'After=systemd-fsck@dev-vdb.service' /run/systemd/generator/mnt.mount"
+        )
+  '';
+}
diff --git a/nixpkgs/nixos/tests/fscrypt.nix b/nixpkgs/nixos/tests/fscrypt.nix
new file mode 100644
index 000000000000..03367979359b
--- /dev/null
+++ b/nixpkgs/nixos/tests/fscrypt.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ ... }:
+{
+  name = "fscrypt";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    security.pam.enableFscrypt = true;
+  };
+
+  testScript = ''
+    def login_as_alice():
+        machine.wait_until_tty_matches("1", "login: ")
+        machine.send_chars("alice\n")
+        machine.wait_until_tty_matches("1", "Password: ")
+        machine.send_chars("foobar\n")
+        machine.wait_until_tty_matches("1", "alice\@machine")
+
+
+    def logout():
+        machine.send_chars("logout\n")
+        machine.wait_until_tty_matches("1", "login: ")
+
+
+    machine.wait_for_unit("default.target")
+
+    with subtest("Enable fscrypt on filesystem"):
+        machine.succeed("tune2fs -O encrypt /dev/vda")
+        machine.succeed("fscrypt setup --quiet --force --time=1ms")
+
+    with subtest("Set up alice with an fscrypt-enabled home directory"):
+        machine.succeed("(echo foobar; echo foobar) | passwd alice")
+        machine.succeed("chown -R alice.users ~alice")
+        machine.succeed("echo foobar | fscrypt encrypt --skip-unlock --source=pam_passphrase --user=alice /home/alice")
+
+    with subtest("Create file as alice"):
+      login_as_alice()
+      machine.succeed("echo hello > /home/alice/world")
+      logout()
+      # Wait for logout to be processed
+      machine.sleep(1)
+
+    with subtest("File should not be readable without being logged in as alice"):
+      machine.fail("cat /home/alice/world")
+
+    with subtest("File should be readable again as alice"):
+      login_as_alice()
+      machine.succeed("cat /home/alice/world")
+      logout()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ft2-clone.nix b/nixpkgs/nixos/tests/ft2-clone.nix
new file mode 100644
index 000000000000..5476b38c00bd
--- /dev/null
+++ b/nixpkgs/nixos/tests/ft2-clone.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "ft2-clone";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    sound.enable = true;
+    environment.systemPackages = [ pkgs.ft2-clone ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      # Add a dummy sound card, or the program won't start
+      machine.execute("modprobe snd-dummy")
+
+      machine.execute("ft2-clone >&2 &")
+
+      machine.wait_for_window(r"Fasttracker")
+      machine.sleep(5)
+      machine.wait_for_text(r"(Songlen|Repstart|Time|About|Nibbles|Help)")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/garage/basic.nix b/nixpkgs/nixos/tests/garage/basic.nix
new file mode 100644
index 000000000000..88d747ea33b9
--- /dev/null
+++ b/nixpkgs/nixos/tests/garage/basic.nix
@@ -0,0 +1,98 @@
+args@{ mkNode, ver, ... }:
+(import ../make-test-python.nix ({ pkgs, ...} : {
+  name = "garage-basic";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+  };
+
+  nodes = {
+    single_node = mkNode { replicationMode = "none"; };
+  };
+
+  testScript = ''
+    from typing import List
+    from dataclasses import dataclass
+    import re
+
+    start_all()
+
+    cur_version_regex = re.compile('Current cluster layout version: (?P<ver>\d*)')
+    key_creation_regex = re.compile('Key name: (?P<key_name>.*)\nKey ID: (?P<key_id>.*)\nSecret key: (?P<secret_key>.*)')
+
+    @dataclass
+    class S3Key:
+       key_name: str
+       key_id: str
+       secret_key: str
+
+    @dataclass
+    class GarageNode:
+       node_id: str
+       host: str
+
+    def get_node_fqn(machine: Machine) -> GarageNode:
+      node_id, host = machine.succeed("garage node id").split('@')
+      return GarageNode(node_id=node_id, host=host)
+
+    def get_node_id(machine: Machine) -> str:
+      return get_node_fqn(machine).node_id
+
+    def get_layout_version(machine: Machine) -> int:
+      version_data = machine.succeed("garage layout show")
+      m = cur_version_regex.search(version_data)
+      if m and m.group('ver') is not None:
+        return int(m.group('ver')) + 1
+      else:
+        raise ValueError('Cannot find current layout version')
+
+    def apply_garage_layout(machine: Machine, layouts: List[str]):
+       for layout in layouts:
+          machine.succeed(f"garage layout assign {layout}")
+       version = get_layout_version(machine)
+       machine.succeed(f"garage layout apply --version {version}")
+
+    def create_api_key(machine: Machine, key_name: str) -> S3Key:
+       output = machine.succeed(f"garage key ${if ver == "0_8" then "new --name" else "create"} {key_name}")
+       m = key_creation_regex.match(output)
+       if not m or not m.group('key_id') or not m.group('secret_key'):
+          raise ValueError('Cannot parse API key data')
+       return S3Key(key_name=key_name, key_id=m.group('key_id'), secret_key=m.group('secret_key'))
+
+    def get_api_key(machine: Machine, key_pattern: str) -> S3Key:
+       output = machine.succeed(f"garage key info {key_pattern}")
+       m = key_creation_regex.match(output)
+       if not m or not m.group('key_name') or not m.group('key_id') or not m.group('secret_key'):
+           raise ValueError('Cannot parse API key data')
+       return S3Key(key_name=m.group('key_name'), key_id=m.group('key_id'), secret_key=m.group('secret_key'))
+
+    def test_bucket_writes(node):
+      node.succeed("garage bucket create test-bucket")
+      s3_key = create_api_key(node, "test-api-key")
+      node.succeed("garage bucket allow --read --write test-bucket --key test-api-key")
+      other_s3_key = get_api_key(node, 'test-api-key')
+      assert other_s3_key.secret_key == other_s3_key.secret_key
+      node.succeed(
+        f"mc alias set test-garage http://[::1]:3900 {s3_key.key_id} {s3_key.secret_key} --api S3v4"
+      )
+      node.succeed("echo test | mc pipe test-garage/test-bucket/test.txt")
+      assert node.succeed("mc cat test-garage/test-bucket/test.txt").strip() == "test"
+
+    def test_bucket_over_http(node, bucket='test-bucket', url=None):
+      if url is None:
+         url = f"{bucket}.web.garage"
+
+      node.succeed(f'garage bucket website --allow {bucket}')
+      node.succeed(f'echo hello world | mc pipe test-garage/{bucket}/index.html')
+      assert (node.succeed(f"curl -H 'Host: {url}' http://localhost:3902")).strip() == 'hello world'
+
+    with subtest("Garage works as a single-node S3 storage"):
+      single_node.wait_for_unit("garage.service")
+      single_node.wait_for_open_port(3900)
+      # Now Garage is initialized.
+      single_node_id = get_node_id(single_node)
+      apply_garage_layout(single_node, [f'-z qemutest -c ${if ver == "0_8" then "1" else "1G"} "{single_node_id}"'])
+      # Now Garage is operational.
+      test_bucket_writes(single_node)
+      test_bucket_over_http(single_node)
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/garage/default.nix b/nixpkgs/nixos/tests/garage/default.nix
new file mode 100644
index 000000000000..a42236e9a5bb
--- /dev/null
+++ b/nixpkgs/nixos/tests/garage/default.nix
@@ -0,0 +1,54 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+with pkgs.lib;
+
+let
+    mkNode = package: { replicationMode, publicV6Address ? "::1" }: { pkgs, ... }: {
+      networking.interfaces.eth1.ipv6.addresses = [{
+        address = publicV6Address;
+        prefixLength = 64;
+      }];
+
+      networking.firewall.allowedTCPPorts = [ 3901 3902 ];
+
+      services.garage = {
+        enable = true;
+        inherit package;
+        settings = {
+          replication_mode = replicationMode;
+
+          rpc_bind_addr = "[::]:3901";
+          rpc_public_addr = "[${publicV6Address}]:3901";
+          rpc_secret = "5c1915fa04d0b6739675c61bf5907eb0fe3d9c69850c83820f51b4d25d13868c";
+
+          s3_api = {
+            s3_region = "garage";
+            api_bind_addr = "[::]:3900";
+            root_domain = ".s3.garage";
+          };
+
+          s3_web = {
+            bind_addr = "[::]:3902";
+            root_domain = ".web.garage";
+            index = "index.html";
+          };
+        };
+      };
+      environment.systemPackages = [ pkgs.minio-client ];
+
+      # Garage requires at least 1GiB of free disk space to run.
+      virtualisation.diskSize = 2 * 1024;
+    };
+in
+  foldl
+  (matrix: ver: matrix // {
+    "basic${toString ver}" = import ./basic.nix { inherit system pkgs ver; mkNode = mkNode pkgs."garage_${ver}"; };
+    "with-3node-replication${toString ver}" = import ./with-3node-replication.nix { inherit system pkgs ver; mkNode = mkNode pkgs."garage_${ver}"; };
+  })
+  {}
+  [
+    "0_8"
+    "0_9"
+  ]
diff --git a/nixpkgs/nixos/tests/garage/with-3node-replication.nix b/nixpkgs/nixos/tests/garage/with-3node-replication.nix
new file mode 100644
index 000000000000..d4387b198d97
--- /dev/null
+++ b/nixpkgs/nixos/tests/garage/with-3node-replication.nix
@@ -0,0 +1,121 @@
+args@{ mkNode, ver, ... }:
+(import ../make-test-python.nix ({ pkgs, ...} :
+{
+  name = "garage-3node-replication";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+  };
+
+  nodes = {
+    node1 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::1"; };
+    node2 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::2"; };
+    node3 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::3"; };
+    node4 = mkNode { replicationMode = 3; publicV6Address = "fc00:1::4"; };
+  };
+
+  testScript = ''
+    from typing import List
+    from dataclasses import dataclass
+    import re
+    start_all()
+
+    cur_version_regex = re.compile('Current cluster layout version: (?P<ver>\d*)')
+    key_creation_regex = re.compile('Key name: (?P<key_name>.*)\nKey ID: (?P<key_id>.*)\nSecret key: (?P<secret_key>.*)')
+
+    @dataclass
+    class S3Key:
+       key_name: str
+       key_id: str
+       secret_key: str
+
+    @dataclass
+    class GarageNode:
+       node_id: str
+       host: str
+
+    def get_node_fqn(machine: Machine) -> GarageNode:
+      node_id, host = machine.succeed("garage node id").split('@')
+      return GarageNode(node_id=node_id, host=host)
+
+    def get_node_id(machine: Machine) -> str:
+      return get_node_fqn(machine).node_id
+
+    def get_layout_version(machine: Machine) -> int:
+      version_data = machine.succeed("garage layout show")
+      m = cur_version_regex.search(version_data)
+      if m and m.group('ver') is not None:
+        return int(m.group('ver')) + 1
+      else:
+        raise ValueError('Cannot find current layout version')
+
+    def apply_garage_layout(machine: Machine, layouts: List[str]):
+       for layout in layouts:
+          machine.succeed(f"garage layout assign {layout}")
+       version = get_layout_version(machine)
+       machine.succeed(f"garage layout apply --version {version}")
+
+    def create_api_key(machine: Machine, key_name: str) -> S3Key:
+       output = machine.succeed(f"garage key ${if ver == "0_8" then "new --name" else "create"} {key_name}")
+       m = key_creation_regex.match(output)
+       if not m or not m.group('key_id') or not m.group('secret_key'):
+          raise ValueError('Cannot parse API key data')
+       return S3Key(key_name=key_name, key_id=m.group('key_id'), secret_key=m.group('secret_key'))
+
+    def get_api_key(machine: Machine, key_pattern: str) -> S3Key:
+       output = machine.succeed(f"garage key info {key_pattern}")
+       m = key_creation_regex.match(output)
+       if not m or not m.group('key_name') or not m.group('key_id') or not m.group('secret_key'):
+           raise ValueError('Cannot parse API key data')
+       return S3Key(key_name=m.group('key_name'), key_id=m.group('key_id'), secret_key=m.group('secret_key'))
+
+    def test_bucket_writes(node):
+      node.succeed("garage bucket create test-bucket")
+      s3_key = create_api_key(node, "test-api-key")
+      node.succeed("garage bucket allow --read --write test-bucket --key test-api-key")
+      other_s3_key = get_api_key(node, 'test-api-key')
+      assert other_s3_key.secret_key == other_s3_key.secret_key
+      node.succeed(
+        f"mc alias set test-garage http://[::1]:3900 {s3_key.key_id} {s3_key.secret_key} --api S3v4"
+      )
+      node.succeed("echo test | mc pipe test-garage/test-bucket/test.txt")
+      assert node.succeed("mc cat test-garage/test-bucket/test.txt").strip() == "test"
+
+    def test_bucket_over_http(node, bucket='test-bucket', url=None):
+      if url is None:
+         url = f"{bucket}.web.garage"
+
+      node.succeed(f'garage bucket website --allow {bucket}')
+      node.succeed(f'echo hello world | mc pipe test-garage/{bucket}/index.html')
+      assert (node.succeed(f"curl -H 'Host: {url}' http://localhost:3902")).strip() == 'hello world'
+
+    with subtest("Garage works as a multi-node S3 storage"):
+      nodes = ('node1', 'node2', 'node3', 'node4')
+      rev_machines = {m.name: m for m in machines}
+      def get_machine(key): return rev_machines[key]
+      for key in nodes:
+        node = get_machine(key)
+        node.wait_for_unit("garage.service")
+        node.wait_for_open_port(3900)
+
+      # Garage is initialized on all nodes.
+      node_ids = {key: get_node_fqn(get_machine(key)) for key in nodes}
+
+      for key in nodes:
+        for other_key in nodes:
+          if other_key != key:
+            other_id = node_ids[other_key]
+            get_machine(key).succeed(f"garage node connect {other_id.node_id}@{other_id.host}")
+
+      # Provide multiple zones for the nodes.
+      zones = ["nixcon", "nixcon", "paris_meetup", "fosdem"]
+      apply_garage_layout(node1,
+      [
+        f'{ndata.node_id} -z {zones[index]} -c ${if ver == "0_8" then "1" else "1G"}'
+        for index, ndata in enumerate(node_ids.values())
+      ])
+      # Now Garage is operational.
+      test_bucket_writes(node1)
+      for node in nodes:
+         test_bucket_over_http(get_machine(node))
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/gemstash.nix b/nixpkgs/nixos/tests/gemstash.nix
new file mode 100644
index 000000000000..bc152e42e92e
--- /dev/null
+++ b/nixpkgs/nixos/tests/gemstash.nix
@@ -0,0 +1,51 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let common_meta = { maintainers = [ maintainers.viraptor ]; };
+in
+{
+  gemstash_works = makeTest {
+    name = "gemstash-works";
+    meta = common_meta;
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.gemstash = {
+        enable = true;
+      };
+    };
+
+    # gemstash responds to http requests
+    testScript = ''
+      machine.wait_for_unit("gemstash.service")
+      machine.wait_for_file("/var/lib/gemstash")
+      machine.wait_for_open_port(9292)
+      machine.succeed("curl http://localhost:9292")
+    '';
+  };
+
+  gemstash_custom_port = makeTest {
+    name = "gemstash-custom-port";
+    meta = common_meta;
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.gemstash = {
+        enable = true;
+        openFirewall = true;
+        settings = {
+          bind = "tcp://0.0.0.0:12345";
+        };
+      };
+    };
+
+    # gemstash responds to http requests
+    testScript = ''
+      machine.wait_for_unit("gemstash.service")
+      machine.wait_for_file("/var/lib/gemstash")
+      machine.wait_for_open_port(12345)
+      machine.succeed("curl http://localhost:12345")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/geoserver.nix b/nixpkgs/nixos/tests/geoserver.nix
new file mode 100644
index 000000000000..4f6f2b209d07
--- /dev/null
+++ b/nixpkgs/nixos/tests/geoserver.nix
@@ -0,0 +1,79 @@
+{ pkgs, lib, ... }:
+
+let
+  geoserver = pkgs.geoserver;
+  geoserverWithImporterExtension = pkgs.geoserver.withExtensions (ps: with ps; [ importer ]);
+
+  # Blacklisted extensions:
+  # - wps-jdbc needs a running (Postrgres) db server.
+  blacklist = [ "wps-jdbc" ];
+
+  blacklistedToNull = n: v: if ! builtins.elem n blacklist then v else null;
+  getNonBlackistedExtensionsAsList = ps: builtins.filter (x: x != null) (lib.attrsets.mapAttrsToList blacklistedToNull ps);
+  geoserverWithAllExtensions = pkgs.geoserver.withExtensions (ps: getNonBlackistedExtensionsAsList ps);
+in
+{
+
+  name = "geoserver";
+  meta = {
+    maintainers = with lib; [ teams.geospatial.members ];
+  };
+
+  nodes = {
+    machine = { pkgs, ... }: {
+      virtualisation.diskSize = 2 * 1024;
+
+      environment.systemPackages = [
+        geoserver
+        geoserverWithImporterExtension
+        geoserverWithAllExtensions
+      ];
+    };
+  };
+
+  testScript = ''
+    from contextlib import contextmanager
+
+    curl_cmd = "curl --fail --connect-timeout 2"
+    curl_cmd_rest = f"{curl_cmd} -u admin:geoserver -X GET"
+    base_url = "http://localhost:8080/geoserver"
+    log_file = "./log.txt"
+
+    @contextmanager
+    def running_geoserver(pkg):
+      try:
+        print(f"Launching geoserver from {pkg}...")
+        machine.execute(f"{pkg}/bin/geoserver-startup > {log_file} 2>&1 &")
+        machine.wait_until_succeeds(f"{curl_cmd} {base_url} 2>&1", timeout=60)
+        yield
+      finally:
+        # We need to wait a little bit to make sure the server is properly
+        # shutdown before launching a new instance.
+        machine.execute(f"{pkg}/bin/geoserver-shutdown; sleep 1")
+
+    start_all()
+
+    with running_geoserver("${geoserver}"):
+      machine.succeed(f"{curl_cmd} {base_url}/ows?service=WMS&version=1.3.0&request=GetCapabilities")
+
+      # No extensions yet.
+      machine.fail(f"{curl_cmd_rest} {base_url}/rest/imports")
+      machine.fail(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv")
+
+
+    with running_geoserver("${geoserverWithImporterExtension}"):
+      machine.succeed(f"{curl_cmd_rest} {base_url}/rest/imports")
+      machine.fail(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv")
+
+    with running_geoserver("${geoserverWithAllExtensions}"):
+      machine.succeed(f"{curl_cmd_rest} {base_url}/rest/imports")
+      machine.succeed(f"{curl_cmd_rest} {base_url}/rest/monitor/requests.csv")
+      _, stdout = machine.execute(f"cat {log_file}")
+      print(stdout.replace("\\n", "\n"))
+      assert "GDAL Native Library loaded" in stdout, "gdal"
+      assert "The turbo jpeg encoder is available for usage" in stdout, "libjpeg-turbo"
+      assert "org.geotools.imageio.netcdf.utilities.NetCDFUtilities" in stdout, "netcdf"
+      assert "Unable to load library 'netcdf'" not in stdout, "netcdf"
+
+  '';
+}
diff --git a/nixpkgs/nixos/tests/gerrit.nix b/nixpkgs/nixos/tests/gerrit.nix
new file mode 100644
index 000000000000..8ae9e89cf6b0
--- /dev/null
+++ b/nixpkgs/nixos/tests/gerrit.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  lfs = pkgs.fetchurl {
+    url = "https://gerrit-ci.gerritforge.com/job/plugin-lfs-bazel-master/90/artifact/bazel-bin/plugins/lfs/lfs.jar";
+    sha256 = "023b0kd8djm3cn1lf1xl67yv3j12yl8bxccn42lkfmwxjwjfqw6h";
+  };
+
+in {
+  name = "gerrit";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ flokli zimbatm ];
+  };
+
+  nodes = {
+    server =
+      { config, pkgs, ... }: {
+        networking.firewall.allowedTCPPorts = [ 80 2222 ];
+
+
+        services.gerrit = {
+          enable = true;
+          serverId = "aa76c84b-50b0-4711-a0a0-1ee30e45bbd0";
+          listenAddress = "[::]:80";
+          jvmHeapLimit = "1g";
+
+          plugins = [ lfs ];
+          builtinPlugins = [ "hooks" "webhooks" ];
+          settings = {
+            gerrit.canonicalWebUrl = "http://server";
+            lfs.plugin = "lfs";
+            plugins.allowRemoteAdmin = true;
+            sshd.listenAddress = "[::]:2222";
+            sshd.advertisedAddress = "[::]:2222";
+          };
+        };
+      };
+
+    client =
+      { ... }: {
+      };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("gerrit.service")
+    server.wait_for_open_port(80)
+    client.succeed("curl http://server")
+
+    server.wait_for_open_port(2222)
+    client.succeed("nc -z server 2222")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/geth.nix b/nixpkgs/nixos/tests/geth.nix
new file mode 100644
index 000000000000..dc6490db57c9
--- /dev/null
+++ b/nixpkgs/nixos/tests/geth.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "geth";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [bachp ];
+  };
+
+  nodes.machine = { ... }: {
+    services.geth."mainnet" = {
+      enable = true;
+      http = {
+        enable = true;
+      };
+    };
+    services.geth."testnet" = {
+      enable = true;
+      port = 30304;
+      network = "goerli";
+      http = {
+        enable = true;
+        port = 18545;
+      };
+      authrpc = {
+        enable = true;
+        port = 18551;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("geth-mainnet.service")
+    machine.wait_for_unit("geth-testnet.service")
+    machine.wait_for_open_port(8545)
+    machine.wait_for_open_port(18545)
+
+    machine.succeed(
+        'geth attach --exec "eth.blockNumber" http://localhost:8545 | grep \'^0$\' '
+    )
+
+    machine.succeed(
+        'geth attach --exec "eth.blockNumber" http://localhost:18545 | grep \'^0$\' '
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ghostunnel.nix b/nixpkgs/nixos/tests/ghostunnel.nix
new file mode 100644
index 000000000000..91a7b7085f67
--- /dev/null
+++ b/nixpkgs/nixos/tests/ghostunnel.nix
@@ -0,0 +1,104 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "ghostunnel";
+  nodes = {
+    backend = { pkgs, ... }: {
+      services.nginx.enable = true;
+      services.nginx.virtualHosts."backend".root = pkgs.runCommand "webroot" {} ''
+        mkdir $out
+        echo hi >$out/hi.txt
+      '';
+      networking.firewall.allowedTCPPorts = [ 80 ];
+    };
+    service = { ... }: {
+      services.ghostunnel.enable = true;
+      services.ghostunnel.servers."plain-old" = {
+        listen = "0.0.0.0:443";
+        cert = "/root/service-cert.pem";
+        key = "/root/service-key.pem";
+        disableAuthentication = true;
+        target = "backend:80";
+        unsafeTarget = true;
+      };
+      services.ghostunnel.servers."client-cert" = {
+        listen = "0.0.0.0:1443";
+        cert = "/root/service-cert.pem";
+        key = "/root/service-key.pem";
+        cacert = "/root/ca.pem";
+        target = "backend:80";
+        allowCN = ["client"];
+        unsafeTarget = true;
+      };
+      networking.firewall.allowedTCPPorts = [ 443 1443 ];
+    };
+    client = { pkgs, ... }: {
+      environment.systemPackages = [
+        pkgs.curl
+      ];
+    };
+  };
+
+  testScript = ''
+
+    # prepare certificates
+
+    def cmd(command):
+      print(f"+{command}")
+      r = os.system(command)
+      if r != 0:
+        raise Exception(f"Command {command} failed with exit code {r}")
+
+    # Create CA
+    cmd("${pkgs.openssl}/bin/openssl genrsa -out ca-key.pem 4096")
+    cmd("${pkgs.openssl}/bin/openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -subj '/C=NL/ST=Zuid-Holland/L=The Hague/O=Stevige Balken en Planken B.V./OU=OpSec/CN=Certificate Authority' -out ca.pem")
+
+    # Create service
+    cmd("${pkgs.openssl}/bin/openssl genrsa -out service-key.pem 4096")
+    cmd("${pkgs.openssl}/bin/openssl req -subj '/CN=service' -sha256 -new -key service-key.pem -out service.csr")
+    cmd("echo subjectAltName = DNS:service,IP:127.0.0.1 >> extfile.cnf")
+    cmd("echo extendedKeyUsage = serverAuth >> extfile.cnf")
+    cmd("${pkgs.openssl}/bin/openssl x509 -req -days 365 -sha256 -in service.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out service-cert.pem -extfile extfile.cnf")
+
+    # Create client
+    cmd("${pkgs.openssl}/bin/openssl genrsa -out client-key.pem 4096")
+    cmd("${pkgs.openssl}/bin/openssl req -subj '/CN=client' -new -key client-key.pem -out client.csr")
+    cmd("echo extendedKeyUsage = clientAuth > extfile-client.cnf")
+    cmd("${pkgs.openssl}/bin/openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile extfile-client.cnf")
+
+    cmd("ls -al")
+
+    start_all()
+
+    # Configuration
+    service.copy_from_host("ca.pem", "/root/ca.pem")
+    service.copy_from_host("service-cert.pem", "/root/service-cert.pem")
+    service.copy_from_host("service-key.pem", "/root/service-key.pem")
+    client.copy_from_host("ca.pem", "/root/ca.pem")
+    client.copy_from_host("service-cert.pem", "/root/service-cert.pem")
+    client.copy_from_host("client-cert.pem", "/root/client-cert.pem")
+    client.copy_from_host("client-key.pem", "/root/client-key.pem")
+
+    backend.wait_for_unit("nginx.service")
+    service.wait_for_unit("multi-user.target")
+    service.wait_for_unit("multi-user.target")
+    client.wait_for_unit("multi-user.target")
+
+    # Check assumptions before the real test
+    client.succeed("bash -c 'diff <(curl -v --no-progress-meter http://backend/hi.txt) <(echo hi)'")
+
+    # Plain old simple TLS can connect, ignoring cert
+    client.succeed("bash -c 'diff <(curl -v --no-progress-meter --insecure https://service/hi.txt) <(echo hi)'")
+
+    # Plain old simple TLS provides correct signature with its cert
+    client.succeed("bash -c 'diff <(curl -v --no-progress-meter --cacert /root/ca.pem https://service/hi.txt) <(echo hi)'")
+
+    # Client can authenticate with certificate
+    client.succeed("bash -c 'diff <(curl -v --no-progress-meter --cert /root/client-cert.pem --key /root/client-key.pem --cacert /root/ca.pem https://service:1443/hi.txt) <(echo hi)'")
+
+    # Client must authenticate with certificate
+    client.fail("bash -c 'diff <(curl -v --no-progress-meter --cacert /root/ca.pem https://service:1443/hi.txt) <(echo hi)'")
+  '';
+
+  meta.maintainers = with pkgs.lib.maintainers; [
+    roberth
+  ];
+})
diff --git a/nixpkgs/nixos/tests/git/hub.nix b/nixpkgs/nixos/tests/git/hub.nix
new file mode 100644
index 000000000000..4f3189861a00
--- /dev/null
+++ b/nixpkgs/nixos/tests/git/hub.nix
@@ -0,0 +1,17 @@
+import ../make-test-python.nix ({ pkgs, ...} : {
+  name = "hub";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  nodes.hub = { pkgs, ... }:
+    {
+      environment.systemPackages = [ pkgs.hub ];
+    };
+
+  testScript =
+    ''
+      assert "git version ${pkgs.git.version}\nhub version ${pkgs.hub.version}\n" in hub.succeed("hub version")
+      assert "These GitHub commands are provided by hub" in hub.succeed("hub help")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gitdaemon.nix b/nixpkgs/nixos/tests/gitdaemon.nix
new file mode 100644
index 000000000000..052fa902b450
--- /dev/null
+++ b/nixpkgs/nixos/tests/gitdaemon.nix
@@ -0,0 +1,74 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  hashes = pkgs.writeText "hashes" ''
+    b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c  /project/bar
+  '';
+in {
+  name = "gitdaemon";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tilpner ];
+  };
+
+  nodes = {
+    server =
+      { config, ... }: {
+        networking.firewall.allowedTCPPorts = [ config.services.gitDaemon.port ];
+
+        environment.systemPackages = [ pkgs.git ];
+
+        systemd.tmpfiles.rules = [
+          # type path mode user group age arg
+          " d    /git 0755 root root  -   -"
+        ];
+
+        services.gitDaemon = {
+          enable = true;
+          basePath = "/git";
+        };
+      };
+
+    client =
+      { pkgs, ... }: {
+        environment.systemPackages = [ pkgs.git ];
+      };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("create project.git"):
+        server.succeed(
+            "git init --bare /git/project.git",
+            "touch /git/project.git/git-daemon-export-ok",
+        )
+
+    with subtest("add file to project.git"):
+        server.succeed(
+            "git clone /git/project.git /project",
+            "echo foo > /project/bar",
+            "git config --global user.email 'you@example.com'",
+            "git config --global user.name 'Your Name'",
+            "git -C /project add bar",
+            "git -C /project commit -m 'quux'",
+            "git -C /project push",
+            "rm -r /project",
+        )
+
+    with subtest("git daemon starts"):
+        server.wait_for_unit("git-daemon.service")
+
+
+    server.systemctl("start network-online.target")
+    client.systemctl("start network-online.target")
+    server.wait_for_unit("network-online.target")
+    client.wait_for_unit("network-online.target")
+
+    with subtest("client can clone project.git"):
+        client.succeed(
+            "git clone git://server/project.git /project",
+            "sha256sum -c ${hashes}",
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gitea.nix b/nixpkgs/nixos/tests/gitea.nix
new file mode 100644
index 000000000000..f62c72bddddc
--- /dev/null
+++ b/nixpkgs/nixos/tests/gitea.nix
@@ -0,0 +1,165 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  giteaPackage ? pkgs.gitea,
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  ## gpg --faked-system-time='20230301T010000!' --quick-generate-key snakeoil ed25519 sign
+  signingPrivateKey = ''
+    -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+    lFgEY/6jkBYJKwYBBAHaRw8BAQdADXiZRV8RJUyC9g0LH04wLMaJL9WTc+szbMi7
+    5fw4yP8AAQCl8EwGfzSLm/P6fCBfA3I9znFb3MEHGCCJhJ6VtKYyRw7ktAhzbmFr
+    ZW9pbIiUBBMWCgA8FiEE+wUM6VW/NLtAdSixTWQt6LZ4x50FAmP+o5ACGwMFCQPC
+    ZwAECwkIBwQVCgkIBRYCAwEAAh4FAheAAAoJEE1kLei2eMedFTgBAKQs1oGFZrCI
+    TZP42hmBTKxGAI1wg7VSdDEWTZxut/2JAQDGgo2sa4VHMfj0aqYGxrIwfP2B7JHO
+    GCqGCRf9O/hzBA==
+    =9Uy3
+    -----END PGP PRIVATE KEY BLOCK-----
+  '';
+  signingPrivateKeyId = "4D642DE8B678C79D";
+
+  supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
+  makeGiteaTest = type: nameValuePair type (makeTest {
+    name = "${giteaPackage.pname}-${type}";
+    meta.maintainers = with maintainers; [ aanderse kolaente ma27 ];
+
+    nodes = {
+      server = { config, pkgs, ... }: {
+        virtualisation.memorySize = 2047;
+        services.gitea = {
+          enable = true;
+          database = { inherit type; };
+          package = giteaPackage;
+          metricsTokenFile = (pkgs.writeText "metrics_secret" "fakesecret").outPath;
+          settings.service.DISABLE_REGISTRATION = true;
+          settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
+          settings.actions.ENABLED = true;
+          settings.metrics.ENABLED = true;
+        };
+        environment.systemPackages = [ giteaPackage pkgs.gnupg pkgs.jq ];
+        services.openssh.enable = true;
+
+        specialisation.runner = {
+          inheritParentConfig = true;
+
+          configuration.services.gitea-actions-runner.instances."test" = {
+            enable = true;
+            name = "ci";
+            url = "http://localhost:3000";
+            labels = [
+              # don't require docker/podman
+              "native:host"
+            ];
+            tokenFile = "/var/lib/gitea/runner_token";
+          };
+        };
+      };
+      client1 = { config, pkgs, ... }: {
+        environment.systemPackages = [ pkgs.git ];
+      };
+      client2 = { config, pkgs, ... }: {
+        environment.systemPackages = [ pkgs.git ];
+      };
+    };
+
+    testScript = { nodes, ... }: let
+      inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+      serverSystem = nodes.server.system.build.toplevel;
+    in ''
+      GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no"
+      REPO = "gitea@server:test/repo"
+      PRIVK = "${snakeOilPrivateKey}"
+
+      start_all()
+
+      client1.succeed("mkdir /tmp/repo")
+      client1.succeed("mkdir -p $HOME/.ssh")
+      client1.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
+      client1.succeed("chmod 0400 $HOME/.ssh/privk")
+      client1.succeed("git -C /tmp/repo init")
+      client1.succeed("echo hello world > /tmp/repo/testfile")
+      client1.succeed("git -C /tmp/repo add .")
+      client1.succeed("git config --global user.email test@localhost")
+      client1.succeed("git config --global user.name test")
+      client1.succeed("git -C /tmp/repo commit -m 'Initial import'")
+      client1.succeed(f"git -C /tmp/repo remote add origin {REPO}")
+
+      server.wait_for_unit("gitea.service")
+      server.wait_for_open_port(3000)
+      server.wait_for_open_port(22)
+      server.succeed("curl --fail http://localhost:3000/")
+
+      server.succeed(
+          "su -l gitea -c 'gpg --homedir /var/lib/gitea/data/home/.gnupg "
+          + "--import ${toString (pkgs.writeText "gitea.key" signingPrivateKey)}'"
+      )
+
+      assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
+
+      server.succeed(
+          "curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. "
+          + "Please contact your site administrator.'"
+      )
+      server.succeed(
+          "su -l gitea -c 'GITEA_WORK_DIR=/var/lib/gitea gitea admin user create "
+          + "--username test --password totallysafe --email test@localhost'"
+      )
+
+      api_token = server.succeed(
+          "curl --fail -X POST http://test:totallysafe@localhost:3000/api/v1/users/test/tokens "
+          + "-H 'Accept: application/json' -H 'Content-Type: application/json' -d "
+          + "'{\"name\":\"token\",\"scopes\":[\"all\"]}' | jq '.sha1' | xargs echo -n"
+      )
+
+      server.succeed(
+          "curl --fail -X POST http://localhost:3000/api/v1/user/repos "
+          + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
+          + f"-H 'Authorization: token {api_token}'"
+          + ' -d \'{"auto_init":false, "description":"string", "license":"mit", "name":"repo", "private":false}\'''
+      )
+
+      server.succeed(
+          "curl --fail -X POST http://localhost:3000/api/v1/user/keys "
+          + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
+          + f"-H 'Authorization: token {api_token}'"
+          + ' -d \'{"key":"${snakeOilPublicKey}","read_only":true,"title":"SSH"}\'''
+      )
+
+      client1.succeed(
+          f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git -C /tmp/repo push origin master"
+      )
+
+      client2.succeed("mkdir -p $HOME/.ssh")
+      client2.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
+      client2.succeed("chmod 0400 $HOME/.ssh/privk")
+      client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git clone {REPO}")
+      client2.succeed('test "$(cat repo/testfile | xargs echo -n)" = "hello world"')
+
+      server.wait_until_succeeds(
+          'test "$(curl http://localhost:3000/api/v1/repos/test/repo/commits '
+          + '-H "Accept: application/json" | jq length)" = "1"'
+      )
+
+      with subtest("Testing metrics endpoint"):
+          server.succeed('curl '
+                         + '-H "Authorization: Bearer fakesecret" '
+                         + 'http://localhost:3000/metrics '
+                         + '| grep gitea_accesses')
+
+      with subtest("Testing runner registration"):
+          server.succeed(
+              "su -l gitea -c 'GITEA_WORK_DIR=/var/lib/gitea gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/gitea/runner_token"
+          )
+          server.succeed("${serverSystem}/specialisation/runner/bin/switch-to-configuration test")
+          server.wait_for_unit("gitea-runner-test.service")
+          server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'")
+    '';
+  });
+in
+
+listToAttrs (map makeGiteaTest supportedDbTypes)
diff --git a/nixpkgs/nixos/tests/github-runner.nix b/nixpkgs/nixos/tests/github-runner.nix
new file mode 100644
index 000000000000..033365d6925c
--- /dev/null
+++ b/nixpkgs/nixos/tests/github-runner.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "github-runner";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ veehaitch ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    services.github-runners.test = {
+      enable = true;
+      url = "https://github.com/yaxitech";
+      tokenFile = builtins.toFile "github-runner.token" "not-so-secret";
+    };
+
+    systemd.services.dummy-github-com = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "github-runner-test.service" ];
+      script = "${pkgs.netcat}/bin/nc -Fl 443 | true && touch /tmp/registration-connect";
+    };
+    networking.hosts."127.0.0.1" = [ "api.github.com" ];
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("dummy-github-com")
+
+    try:
+      machine.wait_for_unit("github-runner-test")
+    except Exception:
+      pass
+
+    out = machine.succeed("journalctl -u github-runner-test")
+    assert "Self-hosted runner registration" in out, "did not read runner registration header"
+
+    machine.wait_until_succeeds("test -f /tmp/registration-connect")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gitlab.nix b/nixpkgs/nixos/tests/gitlab.nix
new file mode 100644
index 000000000000..c4d69a56c93a
--- /dev/null
+++ b/nixpkgs/nixos/tests/gitlab.nix
@@ -0,0 +1,437 @@
+# This test runs gitlab and performs the following tests:
+# - Creating users
+# - Pushing commits
+#   - over the API
+#   - over SSH
+# - Creating Merge Requests and merging them
+# - Opening and closing issues.
+# - Downloading repository archives as tar.gz and tar.bz2
+# Run with
+# [nixpkgs]$ nix-build -A nixosTests.gitlab
+
+{ pkgs, lib, ... }:
+
+let
+  inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+  initialRootPassword = "notproduction";
+  rootProjectId = "2";
+
+  aliceUsername = "alice";
+  aliceUserId = "2";
+  alicePassword = "R5twyCgU0uXC71wT9BBTCqLs6HFZ7h3L";
+  aliceProjectId = "1";
+  aliceProjectName = "test-alice";
+
+  bobUsername = "bob";
+  bobUserId = "3";
+  bobPassword = "XwkkBbl2SiIwabQzgcoaTbhsotijEEtF";
+  bobProjectId = "2";
+in {
+  name = "gitlab";
+  meta.maintainers = with lib.maintainers; [ globin yayayayaka ];
+
+  nodes = {
+    gitlab = { ... }: {
+      imports = [ common/user-account.nix ];
+
+      virtualisation.memorySize = 6144;
+      virtualisation.cores = 4;
+      virtualisation.useNixStoreImage = true;
+      virtualisation.writableStore = false;
+
+      systemd.services.gitlab.serviceConfig.Restart = lib.mkForce "no";
+      systemd.services.gitlab-workhorse.serviceConfig.Restart = lib.mkForce "no";
+      systemd.services.gitaly.serviceConfig.Restart = lib.mkForce "no";
+      systemd.services.gitlab-sidekiq.serviceConfig.Restart = lib.mkForce "no";
+
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+        virtualHosts = {
+          localhost = {
+            locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket";
+          };
+        };
+      };
+
+      services.openssh.enable = true;
+
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+      };
+
+      systemd.services.gitlab-backup.environment.BACKUP = "dump";
+
+      services.gitlab = {
+        enable = true;
+        databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4";
+        initialRootPasswordFile = pkgs.writeText "rootPassword" initialRootPassword;
+        smtp.enable = true;
+        pages = {
+          enable = true;
+          settings.pages-domain = "localhost";
+        };
+        extraConfig = {
+          incoming_email = {
+            enabled = true;
+            mailbox = "inbox";
+            address = "alice@localhost";
+            user = "alice";
+            password = "foobar";
+            host = "localhost";
+            port = 143;
+          };
+        };
+        secrets = {
+          secretFile = pkgs.writeText "secret" "Aig5zaic";
+          otpFile = pkgs.writeText "otpsecret" "Riew9mue";
+          dbFile = pkgs.writeText "dbsecret" "we2quaeZ";
+          jwsFile = pkgs.runCommand "oidcKeyBase" {} "${pkgs.openssl}/bin/openssl genrsa 2048 > $out";
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      auth = pkgs.writeText "auth.json" (builtins.toJSON {
+        grant_type = "password";
+        username = "root";
+        password = initialRootPassword;
+      });
+
+      createUserAlice = pkgs.writeText "create-user-alice.json" (builtins.toJSON rec {
+        username = aliceUsername;
+        name = username;
+        email = "alice@localhost";
+        password = alicePassword;
+        skip_confirmation = true;
+      });
+
+      createUserBob = pkgs.writeText "create-user-bob.json" (builtins.toJSON rec {
+        username = bobUsername;
+        name = username;
+        email = "bob@localhost";
+        password = bobPassword;
+        skip_confirmation = true;
+      });
+
+      aliceAuth = pkgs.writeText "alice-auth.json" (builtins.toJSON {
+        grant_type = "password";
+        username = aliceUsername;
+        password = alicePassword;
+      });
+
+      bobAuth = pkgs.writeText "bob-auth.json" (builtins.toJSON {
+        grant_type = "password";
+        username = bobUsername;
+        password = bobPassword;
+      });
+
+      aliceAddSSHKey = pkgs.writeText "alice-add-ssh-key.json" (builtins.toJSON {
+        id = aliceUserId;
+        title = "snakeoil@nixos";
+        key = snakeOilPublicKey;
+      });
+
+      createProjectAlice = pkgs.writeText "create-project-alice.json" (builtins.toJSON {
+        name = aliceProjectName;
+        visibility = "public";
+      });
+
+      putFile = pkgs.writeText "put-file.json" (builtins.toJSON {
+        branch = "master";
+        author_email = "author@example.com";
+        author_name = "Firstname Lastname";
+        content = "some content";
+        commit_message = "create a new file";
+      });
+
+      mergeRequest = pkgs.writeText "merge-request.json" (builtins.toJSON {
+        id = bobProjectId;
+        target_project_id = aliceProjectId;
+        source_branch = "master";
+        target_branch = "master";
+        title = "Add some other file";
+      });
+
+      newIssue = pkgs.writeText "new-issue.json" (builtins.toJSON {
+        title = "useful issue title";
+      });
+
+      closeIssue = pkgs.writeText "close-issue.json" (builtins.toJSON {
+        issue_iid = 1;
+        state_event = "close";
+      });
+
+      # Wait for all GitLab services to be fully started.
+      waitForServices = ''
+        gitlab.wait_for_unit("gitaly.service")
+        gitlab.wait_for_unit("gitlab-workhorse.service")
+        gitlab.wait_for_unit("gitlab-mailroom.service")
+        gitlab.wait_for_unit("gitlab.service")
+        gitlab.wait_for_unit("gitlab-pages.service")
+        gitlab.wait_for_unit("gitlab-sidekiq.service")
+        gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
+        gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
+      '';
+
+      # The actual test of GitLab. Only push data to GitLab if
+      # `doSetup` is is true.
+      test = doSetup: ''
+        GIT_SSH_COMMAND = "ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null"
+
+        gitlab.succeed(
+            "curl -isSf http://gitlab | grep -i location | grep http://gitlab/users/sign_in"
+        )
+        gitlab.succeed(
+            "${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"
+        )
+      '' + lib.optionalString doSetup ''
+        with subtest("Create user Alice"):
+            gitlab.succeed(
+                """[ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createUserAlice} http://gitlab/api/v4/users)" = "201" ]"""
+            )
+            gitlab.succeed(
+                "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${aliceAuth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers-alice"
+            )
+
+        with subtest("Create user Bob"):
+            gitlab.succeed(
+                """ [ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createUserBob} http://gitlab/api/v4/users)" = "201" ]"""
+            )
+            gitlab.succeed(
+                "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${bobAuth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers-bob"
+            )
+
+        with subtest("Setup Git and SSH for Alice"):
+            gitlab.succeed("git config --global user.name Alice")
+            gitlab.succeed("git config --global user.email alice@nixos.invalid")
+            gitlab.succeed("mkdir -m 700 /root/.ssh")
+            gitlab.succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa")
+            gitlab.succeed("chmod 600 /root/.ssh/id_ecdsa")
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice -d @${aliceAddSSHKey} \
+                    http://gitlab/api/v4/user/keys)" = "201" ]
+                """
+            )
+
+        with subtest("Create a new repository"):
+            # Alice creates a new repository
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice \
+                    -d @${createProjectAlice} \
+                    http://gitlab/api/v4/projects)" = "201" ]
+                """
+            )
+
+            # Alice commits an initial commit
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice \
+                    -d @${putFile} \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/files/some-file.txt)" = "201" ]"""
+            )
+
+        with subtest("git clone over HTTP"):
+            gitlab.succeed(
+                """git clone http://gitlab/alice/${aliceProjectName}.git clone-via-http""",
+                timeout=15
+            )
+
+        with subtest("Push a commit via SSH"):
+            gitlab.succeed(
+                f"""GIT_SSH_COMMAND="{GIT_SSH_COMMAND}" git clone gitlab@gitlab:alice/${aliceProjectName}.git""",
+                timeout=15
+            )
+            gitlab.succeed(
+                """echo "a commit sent over ssh" > ${aliceProjectName}/ssh.txt"""
+            )
+            gitlab.succeed(
+                """
+                cd ${aliceProjectName} || exit 1
+                git add .
+                """
+            )
+            gitlab.succeed(
+                """
+                cd ${aliceProjectName} || exit 1
+                git commit -m "Add a commit to be sent over ssh"
+                """
+            )
+            gitlab.succeed(
+                f"""
+                cd ${aliceProjectName} || exit 1
+                GIT_SSH_COMMAND="{GIT_SSH_COMMAND}" git push --set-upstream origin master
+                """,
+                timeout=15
+            )
+
+        with subtest("Fork a project"):
+            # Bob forks Alice's project
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-bob \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/fork)" = "201" ]
+                """
+            )
+
+            # Bob creates a commit
+            gitlab.wait_until_succeeds(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-bob \
+                    -d @${putFile} \
+                    http://gitlab/api/v4/projects/${bobProjectId}/repository/files/some-other-file.txt)" = "201" ]
+                """
+            )
+
+        with subtest("Create a Merge Request"):
+            # Bob opens a merge request against Alice's repository
+            gitlab.wait_until_succeeds(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-bob \
+                    -d @${mergeRequest} \
+                    http://gitlab/api/v4/projects/${bobProjectId}/merge_requests)" = "201" ]
+                """
+            )
+
+            # Alice merges the MR
+            gitlab.wait_until_succeeds(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X PUT \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice \
+                    -d @${mergeRequest} \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/merge_requests/1/merge)" = "200" ]
+                """
+            )
+
+        with subtest("Create an Issue"):
+            # Bob opens an issue on Alice's repository
+            gitlab.succeed(
+                """[ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X POST \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-bob \
+                    -d @${newIssue} \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/issues)" = "201" ]
+                """
+            )
+
+            # Alice closes the issue
+            gitlab.wait_until_succeeds(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -X PUT \
+                    -H 'Content-Type: application/json' \
+                    -H @/tmp/headers-alice -d @${closeIssue} http://gitlab/api/v4/projects/${aliceProjectId}/issues/1)" = "200" ]
+                """
+            )
+      '' + ''
+        with subtest("Download archive.tar.gz"):
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -H @/tmp/headers-alice \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.gz)" = "200" ]
+                """
+            )
+            gitlab.succeed(
+                """
+                curl \
+                    -H @/tmp/headers-alice \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.gz > /tmp/archive.tar.gz
+                """
+            )
+            gitlab.succeed("test -s /tmp/archive.tar.gz")
+
+        with subtest("Download archive.tar.bz2"):
+            gitlab.succeed(
+                """
+                [ "$(curl \
+                    -o /dev/null \
+                    -w '%{http_code}' \
+                    -H @/tmp/headers-alice \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.bz2)" = "200" ]
+                """
+            )
+            gitlab.succeed(
+                """
+                curl \
+                    -H @/tmp/headers-alice \
+                    http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.bz2 > /tmp/archive.tar.bz2
+                """
+            )
+            gitlab.succeed("test -s /tmp/archive.tar.bz2")
+      '';
+
+  in ''
+      gitlab.start()
+    ''
+    + waitForServices
+    + test true
+    + ''
+      gitlab.systemctl("start gitlab-backup.service")
+      gitlab.wait_for_unit("gitlab-backup.service")
+      gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/backup/dump_gitlab_backup.tar")
+      gitlab.systemctl("stop postgresql.service gitlab-config.service gitlab.target")
+      gitlab.succeed(
+          "find ${nodes.gitlab.services.gitlab.statePath} -mindepth 1 -maxdepth 1 -not -name backup -execdir rm -r {} +"
+      )
+      gitlab.succeed("systemd-tmpfiles --create")
+      gitlab.succeed("rm -rf ${nodes.gitlab.services.postgresql.dataDir}")
+      gitlab.systemctl("start gitlab-config.service gitaly.service gitlab-postgresql.service")
+      gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitaly.socket")
+      gitlab.succeed(
+          "sudo -u gitlab -H gitlab-rake gitlab:backup:restore RAILS_ENV=production BACKUP=dump force=yes"
+      )
+      gitlab.systemctl("start gitlab.target")
+    ''
+    + waitForServices
+    + test false;
+}
diff --git a/nixpkgs/nixos/tests/gitolite-fcgiwrap.nix b/nixpkgs/nixos/tests/gitolite-fcgiwrap.nix
new file mode 100644
index 000000000000..abf1db37003a
--- /dev/null
+++ b/nixpkgs/nixos/tests/gitolite-fcgiwrap.nix
@@ -0,0 +1,93 @@
+import ./make-test-python.nix (
+  { pkgs, ... }:
+
+    let
+      user = "gitolite-admin";
+      password = "some_password";
+
+      # not used but needed to setup gitolite
+      adminPublicKey = ''
+        ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7urFhAA90BTpGuEHeWWTY3W/g9PBxXNxfWhfbrm4Le root@client
+      '';
+    in
+      {
+        name = "gitolite-fcgiwrap";
+
+        meta = with pkgs.lib.maintainers; {
+          maintainers = [ bbigras ];
+        };
+
+        nodes = {
+
+          server =
+            { config, ... }:
+              {
+                networking.firewall.allowedTCPPorts = [ 80 ];
+
+                services.fcgiwrap.enable = true;
+                services.gitolite = {
+                  enable = true;
+                  adminPubkey = adminPublicKey;
+                };
+
+                services.nginx = {
+                  enable = true;
+                  recommendedProxySettings = true;
+                  virtualHosts."server".locations."/git".extraConfig = ''
+                    # turn off gzip as git objects are already well compressed
+                    gzip off;
+
+                    # use file based basic authentication
+                    auth_basic "Git Repository Authentication";
+                    auth_basic_user_file /etc/gitolite/htpasswd;
+
+                    # common FastCGI parameters are required
+                    include ${config.services.nginx.package}/conf/fastcgi_params;
+
+                    # strip the CGI program prefix
+                    fastcgi_split_path_info ^(/git)(.*)$;
+                    fastcgi_param PATH_INFO $fastcgi_path_info;
+
+                    # pass authenticated user login(mandatory) to Gitolite
+                    fastcgi_param REMOTE_USER $remote_user;
+
+                    # pass git repository root directory and hosting user directory
+                    # these env variables can be set in a wrapper script
+                    fastcgi_param GIT_HTTP_EXPORT_ALL "";
+                    fastcgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories;
+                    fastcgi_param GITOLITE_HTTP_HOME /var/lib/gitolite;
+                    fastcgi_param SCRIPT_FILENAME ${pkgs.gitolite}/bin/gitolite-shell;
+
+                    # use Unix domain socket or inet socket
+                    fastcgi_pass unix:/run/fcgiwrap.sock;
+                  '';
+                };
+
+                # WARNING: DON'T DO THIS IN PRODUCTION!
+                # This puts unhashed secrets directly into the Nix store for ease of testing.
+                environment.etc."gitolite/htpasswd".source = pkgs.runCommand "htpasswd" {} ''
+                  ${pkgs.apacheHttpd}/bin/htpasswd -bc "$out" ${user} ${password}
+                '';
+              };
+
+          client =
+            { pkgs, ... }:
+              {
+                environment.systemPackages = [ pkgs.git ];
+              };
+        };
+
+        testScript = ''
+          start_all()
+
+          server.wait_for_unit("gitolite-init.service")
+          server.wait_for_unit("nginx.service")
+          server.wait_for_file("/run/fcgiwrap.sock")
+
+          client.wait_for_unit("multi-user.target")
+          client.succeed(
+              "git clone http://${user}:${password}@server/git/gitolite-admin.git"
+          )
+        '';
+      }
+)
diff --git a/nixpkgs/nixos/tests/gitolite.nix b/nixpkgs/nixos/tests/gitolite.nix
new file mode 100644
index 000000000000..9b3af59e4fbd
--- /dev/null
+++ b/nixpkgs/nixos/tests/gitolite.nix
@@ -0,0 +1,138 @@
+import ./make-test-python.nix ({ pkgs, ...}:
+
+let
+  adminPrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACDu7qxYQAPdAU6RrhB3llk2N1v4PTwcVzcX1oX265uC3gAAAJBJiYxDSYmM
+    QwAAAAtzc2gtZWQyNTUxOQAAACDu7qxYQAPdAU6RrhB3llk2N1v4PTwcVzcX1oX265uC3g
+    AAAEDE1W6vMwSEUcF1r7Hyypm/+sCOoDmKZgPxi3WOa1mD2u7urFhAA90BTpGuEHeWWTY3
+    W/g9PBxXNxfWhfbrm4LeAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  adminPublicKey = ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO7urFhAA90BTpGuEHeWWTY3W/g9PBxXNxfWhfbrm4Le root@client
+  '';
+
+  alicePrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBbeWvHh/AWGWI6EIc1xlSihyXtacNQ9KeztlW/VUy8wQAAAJAwVQ5VMFUO
+    VQAAAAtzc2gtZWQyNTUxOQAAACBbeWvHh/AWGWI6EIc1xlSihyXtacNQ9KeztlW/VUy8wQ
+    AAAEB7lbfkkdkJoE+4TKHPdPQWBKLSx+J54Eg8DaTr+3KoSlt5a8eH8BYZYjoQhzXGVKKH
+    Je1pw1D0p7O2Vb9VTLzBAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  alicePublicKey = pkgs.writeText "id_ed25519.pub" ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFt5a8eH8BYZYjoQhzXGVKKHJe1pw1D0p7O2Vb9VTLzB alice@client
+  '';
+
+  bobPrivateKey = pkgs.writeText "id_ed25519" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACCWTaJ1D9Xjxy6759FvQ9oXTes1lmWBciXPkEeqTikBMAAAAJDQBmNV0AZj
+    VQAAAAtzc2gtZWQyNTUxOQAAACCWTaJ1D9Xjxy6759FvQ9oXTes1lmWBciXPkEeqTikBMA
+    AAAEDM1IYYFUwk/IVxauha9kuR6bbRtT3gZ6ZA0GLb9txb/pZNonUP1ePHLrvn0W9D2hdN
+    6zWWZYFyJc+QR6pOKQEwAAAACGJmb0BtaW5pAQIDBAU=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  bobPublicKey = pkgs.writeText "id_ed25519.pub" ''
+    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZNonUP1ePHLrvn0W9D2hdN6zWWZYFyJc+QR6pOKQEw bob@client
+  '';
+
+  gitoliteAdminConfSnippet = pkgs.writeText "gitolite-admin-conf-snippet" ''
+    repo alice-project
+        RW+     =   alice
+  '';
+in
+{
+  name = "gitolite";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ bjornfor ];
+  };
+
+  nodes = {
+
+    server =
+      { ... }:
+      {
+        services.gitolite = {
+          enable = true;
+          adminPubkey = adminPublicKey;
+        };
+        services.openssh.enable = true;
+      };
+
+    client =
+      { pkgs, ... }:
+      {
+        environment.systemPackages = [ pkgs.git ];
+        programs.ssh.extraConfig = ''
+          Host *
+            UserKnownHostsFile /dev/null
+            StrictHostKeyChecking no
+            # there's nobody around that can input password
+            PreferredAuthentications publickey
+        '';
+        users.users.alice = { isNormalUser = true; };
+        users.users.bob = { isNormalUser = true; };
+      };
+
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("can setup ssh keys on system"):
+        client.succeed(
+            "mkdir -p ~root/.ssh",
+            "cp ${adminPrivateKey} ~root/.ssh/id_ed25519",
+            "chmod 600 ~root/.ssh/id_ed25519",
+        )
+        client.succeed(
+            "sudo -u alice mkdir -p ~alice/.ssh",
+            "sudo -u alice cp ${alicePrivateKey} ~alice/.ssh/id_ed25519",
+            "sudo -u alice chmod 600 ~alice/.ssh/id_ed25519",
+        )
+        client.succeed(
+            "sudo -u bob mkdir -p ~bob/.ssh",
+            "sudo -u bob cp ${bobPrivateKey} ~bob/.ssh/id_ed25519",
+            "sudo -u bob chmod 600 ~bob/.ssh/id_ed25519",
+        )
+
+    with subtest("gitolite server starts"):
+        server.wait_for_unit("gitolite-init.service")
+        server.wait_for_unit("sshd.service")
+        client.succeed("ssh -n gitolite@server info")
+
+    with subtest("admin can clone and configure gitolite-admin.git"):
+        client.succeed(
+            "git clone gitolite@server:gitolite-admin.git",
+            "git config --global user.name 'System Administrator'",
+            "git config --global user.email root\@domain.example",
+            "cp ${alicePublicKey} gitolite-admin/keydir/alice.pub",
+            "cp ${bobPublicKey} gitolite-admin/keydir/bob.pub",
+            "(cd gitolite-admin && git add . && git commit -m 'Add keys for alice, bob' && git push)",
+            "cat ${gitoliteAdminConfSnippet} >> gitolite-admin/conf/gitolite.conf",
+            "(cd gitolite-admin && git add . && git commit -m 'Add repo for alice' && git push)",
+        )
+
+    with subtest("non-admins cannot clone gitolite-admin.git"):
+        client.fail("sudo -i -u alice git clone gitolite@server:gitolite-admin.git")
+        client.fail("sudo -i -u bob git clone gitolite@server:gitolite-admin.git")
+
+    with subtest("non-admins can clone testing.git"):
+        client.succeed("sudo -i -u alice git clone gitolite@server:testing.git")
+        client.succeed("sudo -i -u bob git clone gitolite@server:testing.git")
+
+    with subtest("alice can clone alice-project.git"):
+        client.succeed("sudo -i -u alice git clone gitolite@server:alice-project.git")
+
+    with subtest("bob cannot clone alice-project.git"):
+        client.fail("sudo -i -u bob git clone gitolite@server:alice-project.git")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/glusterfs.nix b/nixpkgs/nixos/tests/glusterfs.nix
new file mode 100644
index 000000000000..ef09264a0216
--- /dev/null
+++ b/nixpkgs/nixos/tests/glusterfs.nix
@@ -0,0 +1,68 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+
+let
+  client = { pkgs, ... } : {
+    environment.systemPackages = [ pkgs.glusterfs ];
+    virtualisation.fileSystems =
+      { "/gluster" =
+          { device = "server1:/gv0";
+            fsType = "glusterfs";
+          };
+      };
+  };
+
+  server = { pkgs, ... } : {
+    networking.firewall.enable = false;
+    services.glusterfs.enable = true;
+
+    # create a mount point for the volume
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb
+    '';
+
+    virtualisation.emptyDiskImages = [ 1024 ];
+
+    virtualisation.fileSystems =
+      { "/data" =
+          { device = "/dev/disk/by-label/data";
+            fsType = "ext4";
+          };
+      };
+  };
+in {
+  name = "glusterfs";
+
+  nodes = {
+    server1 = server;
+    server2 = server;
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = ''
+    server1.wait_for_unit("glusterd.service")
+    server2.wait_for_unit("glusterd.service")
+
+    server1.wait_until_succeeds("gluster peer status")
+    server2.wait_until_succeeds("gluster peer status")
+
+    # establish initial contact
+    server1.succeed("gluster peer probe server2")
+    server1.succeed("gluster peer probe server1")
+
+    server1.succeed("gluster peer status | grep Connected")
+
+    # create volumes
+    server1.succeed("mkdir -p /data/vg0")
+    server2.succeed("mkdir -p /data/vg0")
+    server1.succeed("gluster volume create gv0 server1:/data/vg0 server2:/data/vg0")
+    server1.succeed("gluster volume start gv0")
+
+    # test clients
+    client1.wait_for_unit("gluster.mount")
+    client2.wait_for_unit("gluster.mount")
+
+    client1.succeed("echo test > /gluster/file1")
+    client2.succeed("grep test /gluster/file1")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gnome-extensions.nix b/nixpkgs/nixos/tests/gnome-extensions.nix
new file mode 100644
index 000000000000..a9bb5e3766b7
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnome-extensions.nix
@@ -0,0 +1,151 @@
+import ./make-test-python.nix (
+{ pkgs, lib, ...}:
+{
+  name = "gnome-extensions";
+  meta.maintainers = [ lib.maintainers.piegames ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      imports = [ ./common/user-account.nix ];
+
+      # Install all extensions
+      environment.systemPackages = lib.filter (e: e ? extensionUuid) (lib.attrValues pkgs.gnomeExtensions);
+
+      # Some extensions are broken, but that's kind of the point of a testing VM
+      nixpkgs.config.allowBroken = true;
+      # There are some aliases which throw exceptions; ignore them.
+      # Also prevent duplicate extensions under different names.
+      nixpkgs.config.allowAliases = false;
+
+      # Configure GDM
+      services.xserver.enable = true;
+      services.xserver.displayManager = {
+        gdm = {
+          enable = true;
+          debug = true;
+          wayland = true;
+        };
+        autoLogin = {
+          enable = true;
+          user = "alice";
+        };
+      };
+
+      # Configure Gnome
+      services.xserver.desktopManager.gnome.enable = true;
+      services.xserver.desktopManager.gnome.debug = true;
+
+      systemd.user.services = {
+        "org.gnome.Shell@wayland" = {
+          serviceConfig = {
+            ExecStart = [
+              # Clear the list before overriding it.
+              ""
+              # Eval API is now internal so Shell needs to run in unsafe mode.
+              # TODO: improve test driver so that it supports openqa-like manipulation
+              # that would allow us to drop this mess.
+              "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
+            ];
+          };
+        };
+      };
+
+    };
+
+  testScript = { nodes, ... }: let
+    # Keep line widths somewhat manageable
+    user = nodes.machine.users.users.alice;
+    uid = toString user.uid;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
+
+    # Call javascript in gnome shell, returns a tuple (success, output), where
+    # `success` is true if the dbus call was successful and output is what the
+    # javascript evaluates to.
+    eval = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
+
+    # False when startup is done
+    startingUp = eval "Main.layoutManager._startingUp";
+
+    # Extensions to keep always enabled together
+    # Those are extensions that are usually always on for many users, and that we expect to work
+    # well together with most others without conflicts
+    alwaysOnExtensions = map (name: pkgs.gnomeExtensions.${name}.extensionUuid) [
+      "applications-menu"
+      "user-themes"
+    ];
+
+    # Extensions to enable and disable individually
+    # Extensions like dash-to-dock and dash-to-panel cannot be enabled at the same time.
+    testExtensions = map (name: pkgs.gnomeExtensions.${name}.extensionUuid) [
+      "appindicator"
+      "dash-to-dock"
+      "dash-to-panel"
+      "ddterm"
+      "emoji-selector"
+      "gsconnect"
+      "system-monitor-next"
+      "desktop-icons-ng-ding"
+      "workspace-indicator"
+      "vitals"
+    ];
+  in
+  ''
+    with subtest("Login to GNOME with GDM"):
+        # wait for gdm to start
+        machine.wait_for_unit("display-manager.service")
+        # wait for the wayland server
+        machine.wait_for_file("/run/user/${uid}/wayland-0")
+        # wait for alice to be logged in
+        machine.wait_for_unit("default.target", "${user.name}")
+        # check that logging in has given the user ownership of devices
+        assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
+
+    with subtest("Wait for GNOME Shell"):
+        # correct output should be (true, 'false')
+        machine.wait_until_succeeds(
+            "${startingUp} | grep -q 'true,..false'"
+        )
+
+        # Close the Activities view so that Shell can correctly track the focused window.
+        machine.send_key("esc")
+        # # Disable extension version validation (only use for manual testing)
+        # machine.succeed(
+        #   "${run "gsettings set org.gnome.shell disable-extension-version-validation true"}"
+        # )
+
+    # Assert that some extension is in a specific state
+    def checkState(target, extension):
+        state = machine.succeed(
+            f"${run "gnome-extensions info {extension}"} | grep '^  State: .*$'"
+        )
+        assert target in state, f"{state} instead of {target}"
+
+    def checkExtension(extension, disable):
+        with subtest(f"Enable extension '{extension}'"):
+            # Check that the extension is properly initialized; skip out of date ones
+            state = machine.succeed(
+                f"${run "gnome-extensions info {extension}"} | grep '^  State: .*$'"
+            )
+            if "OUT OF DATE" in state:
+                machine.log(f"Extension {extension} will be skipped because out of date")
+                return
+
+            assert "INITIALIZED" in state, f"{state} instead of INITIALIZED"
+
+            # Enable and optionally disable
+
+            machine.succeed(f"${run "gnome-extensions enable {extension}"}")
+            checkState("ENABLED", extension)
+
+            if disable:
+                machine.succeed(f"${run "gnome-extensions disable {extension}"}")
+                checkState("DISABLED", extension)
+    ''
+    + lib.concatLines (map (e: ''checkExtension("${e}", False)'') alwaysOnExtensions)
+    + lib.concatLines (map (e: ''checkExtension("${e}", True)'') testExtensions)
+  ;
+}
+)
diff --git a/nixpkgs/nixos/tests/gnome-flashback.nix b/nixpkgs/nixos/tests/gnome-flashback.nix
new file mode 100644
index 000000000000..f486dabc5c40
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnome-flashback.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "gnome-flashback";
+  meta.maintainers = lib.teams.gnome.members ++ [ lib.maintainers.chpatrick ];
+
+  nodes.machine = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+    in
+
+    { imports = [ ./common/user-account.nix ];
+
+      services.xserver.enable = true;
+
+      services.xserver.displayManager = {
+        gdm.enable = true;
+        gdm.debug = true;
+        autoLogin = {
+          enable = true;
+          user = user.name;
+        };
+      };
+
+      services.xserver.desktopManager.gnome.enable = true;
+      services.xserver.desktopManager.gnome.debug = true;
+      services.xserver.desktopManager.gnome.flashback.enableMetacity = true;
+      services.xserver.displayManager.defaultSession = "gnome-flashback-metacity";
+    };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    uid = toString user.uid;
+    xauthority = "/run/user/${uid}/gdm/Xauthority";
+  in ''
+      with subtest("Login to GNOME Flashback with GDM"):
+          machine.wait_for_x()
+          machine.wait_until_succeeds('journalctl -t gnome-session-binary --grep "Entering running state"')
+          # Wait for alice to be logged in"
+          machine.wait_for_unit("default.target", "${user.name}")
+          machine.wait_for_file("${xauthority}")
+          machine.succeed("xauth merge ${xauthority}")
+          # Check that logging in has given the user ownership of devices
+          assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
+
+      with subtest("Wait for Metacity"):
+          machine.wait_until_succeeds("pgrep metacity")
+
+      with subtest("Regression test for #233920"):
+          machine.wait_until_succeeds("pgrep -fa gnome-flashback-media-keys")
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gnome-xorg.nix b/nixpkgs/nixos/tests/gnome-xorg.nix
new file mode 100644
index 000000000000..6ca700edcac3
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnome-xorg.nix
@@ -0,0 +1,99 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "gnome-xorg";
+  meta = {
+    maintainers = lib.teams.gnome.members;
+  };
+
+  nodes.machine = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+  in
+
+    { imports = [ ./common/user-account.nix ];
+
+      services.xserver.enable = true;
+
+      services.xserver.displayManager = {
+        gdm.enable = true;
+        gdm.debug = true;
+        autoLogin = {
+          enable = true;
+          user = user.name;
+        };
+      };
+
+      services.xserver.desktopManager.gnome.enable = true;
+      services.xserver.desktopManager.gnome.debug = true;
+      services.xserver.displayManager.defaultSession = "gnome-xorg";
+
+      systemd.user.services = {
+        "org.gnome.Shell@x11" = {
+          serviceConfig = {
+            ExecStart = [
+              # Clear the list before overriding it.
+              ""
+              # Eval API is now internal so Shell needs to run in unsafe mode.
+              # TODO: improve test driver so that it supports openqa-like manipulation
+              # that would allow us to drop this mess.
+              "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
+            ];
+          };
+        };
+      };
+
+    };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    uid = toString user.uid;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
+    xauthority = "/run/user/${uid}/gdm/Xauthority";
+    display = "DISPLAY=:0.0";
+    env = "${bus} XAUTHORITY=${xauthority} ${display}";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
+
+    # Call javascript in gnome shell, returns a tuple (success, output), where
+    # `success` is true if the dbus call was successful and output is what the
+    # javascript evaluates to.
+    eval = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
+
+    # False when startup is done
+    startingUp = eval "Main.layoutManager._startingUp";
+
+    # Start Console
+    launchConsole = run "gapplication launch org.gnome.Console";
+
+    # Hopefully Console's wm class
+    wmClass = eval "global.display.focus_window.wm_class";
+  in ''
+      with subtest("Login to GNOME Xorg with GDM"):
+          machine.wait_for_x()
+          # Wait for alice to be logged in"
+          machine.wait_for_unit("default.target", "${user.name}")
+          machine.wait_for_file("${xauthority}")
+          machine.succeed("xauth merge ${xauthority}")
+          # Check that logging in has given the user ownership of devices
+          assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
+
+      with subtest("Wait for GNOME Shell"):
+          # correct output should be (true, 'false')
+          machine.wait_until_succeeds(
+              "${startingUp} | grep -q 'true,..false'"
+          )
+
+      with subtest("Open Console"):
+          # Close the Activities view so that Shell can correctly track the focused window.
+          machine.send_key("esc")
+
+          machine.succeed(
+              "${launchConsole}"
+          )
+          # correct output should be (true, '"kgx"')
+          # For some reason, this deviates from Wayland.
+          machine.wait_until_succeeds(
+              "${wmClass} | grep -q  'true,...kgx'"
+          )
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gnome.nix b/nixpkgs/nixos/tests/gnome.nix
new file mode 100644
index 000000000000..91182790cb24
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnome.nix
@@ -0,0 +1,93 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "gnome";
+  meta.maintainers = lib.teams.gnome.members;
+
+  nodes.machine =
+    { ... }:
+
+    { imports = [ ./common/user-account.nix ];
+
+      services.xserver.enable = true;
+
+      services.xserver.displayManager = {
+        gdm.enable = true;
+        gdm.debug = true;
+        autoLogin = {
+          enable = true;
+          user = "alice";
+        };
+      };
+
+      services.xserver.desktopManager.gnome.enable = true;
+      services.xserver.desktopManager.gnome.debug = true;
+
+      systemd.user.services = {
+        "org.gnome.Shell@wayland" = {
+          serviceConfig = {
+            ExecStart = [
+              # Clear the list before overriding it.
+              ""
+              # Eval API is now internal so Shell needs to run in unsafe mode.
+              # TODO: improve test driver so that it supports openqa-like manipulation
+              # that would allow us to drop this mess.
+              "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
+            ];
+          };
+        };
+      };
+
+    };
+
+  testScript = { nodes, ... }: let
+    # Keep line widths somewhat manageable
+    user = nodes.machine.users.users.alice;
+    uid = toString user.uid;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
+
+    # Call javascript in gnome shell, returns a tuple (success, output), where
+    # `success` is true if the dbus call was successful and output is what the
+    # javascript evaluates to.
+    eval = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
+
+    # False when startup is done
+    startingUp = eval "Main.layoutManager._startingUp";
+
+    # Start Console
+    launchConsole = run "gapplication launch org.gnome.Console";
+
+    # Hopefully Console's wm class
+    wmClass = eval "global.display.focus_window.wm_class";
+  in ''
+      with subtest("Login to GNOME with GDM"):
+          # wait for gdm to start
+          machine.wait_for_unit("display-manager.service")
+          # wait for the wayland server
+          machine.wait_for_file("/run/user/${uid}/wayland-0")
+          # wait for alice to be logged in
+          machine.wait_for_unit("default.target", "${user.name}")
+          # check that logging in has given the user ownership of devices
+          assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
+
+      with subtest("Wait for GNOME Shell"):
+          # correct output should be (true, 'false')
+          machine.wait_until_succeeds(
+              "${startingUp} | grep -q 'true,..false'"
+          )
+
+      with subtest("Open Console"):
+          # Close the Activities view so that Shell can correctly track the focused window.
+          machine.send_key("esc")
+
+          machine.succeed(
+              "${launchConsole}"
+          )
+          # correct output should be (true, '"org.gnome.Console"')
+          machine.wait_until_succeeds(
+              "${wmClass} | grep -q 'true,...org.gnome.Console'"
+          )
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gns3-server.nix b/nixpkgs/nixos/tests/gns3-server.nix
new file mode 100644
index 000000000000..e37d751f5f64
--- /dev/null
+++ b/nixpkgs/nixos/tests/gns3-server.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "gns3-server";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine =
+    { ... }:
+    let
+      tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+        openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 365 \
+          -subj '/CN=localhost'
+        install -D -t $out key.pem cert.pem
+      '';
+    in {
+      services.gns3-server = {
+        enable = true;
+        auth = {
+          enable = true;
+          user = "user";
+          passwordFile = pkgs.writeText "gns3-auth-password-file" "password";
+        };
+        ssl = {
+          enable = true;
+          certFile = "${tls-cert}/cert.pem";
+          keyFile = "${tls-cert}/key.pem";
+        };
+        dynamips.enable = true;
+        ubridge.enable = true;
+        vpcs.enable = true;
+      };
+
+      security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+    };
+
+  testScript = let
+    createProject = pkgs.writeText "createProject.json" (builtins.toJSON {
+      name = "test_project";
+    });
+  in
+  ''
+    start_all()
+
+    machine.wait_for_unit("gns3-server.service")
+    machine.wait_for_open_port(3080)
+
+    with subtest("server is listening"):
+      machine.succeed("curl -sSfL -u user:password https://localhost:3080/v2/version")
+
+    with subtest("create dummy project"):
+      machine.succeed("curl -sSfL -u user:password https://localhost:3080/v2/projects -d @${createProject}")
+
+    with subtest("logging works"):
+      log_path = "/var/log/gns3/server.log"
+      machine.wait_for_file(log_path)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gnupg.nix b/nixpkgs/nixos/tests/gnupg.nix
new file mode 100644
index 000000000000..65a9a93007fd
--- /dev/null
+++ b/nixpkgs/nixos/tests/gnupg.nix
@@ -0,0 +1,118 @@
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+
+{
+  name = "gnupg";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  # server for testing SSH
+  nodes.server = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    users.users.alice.isNormalUser = true;
+    services.openssh.enable = true;
+  };
+
+  # machine for testing GnuPG
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    users.users.alice.isNormalUser = true;
+    services.getty.autologinUser = "alice";
+
+    environment.shellInit = ''
+      # preset a key passphrase in gpg-agent
+      preset_key() {
+        # find all keys
+        case "$1" in
+          ssh) grips=$(awk '/^[0-9A-F]/{print $1}' "''${GNUPGHOME:-$HOME/.gnupg}/sshcontrol") ;;
+          pgp) grips=$(gpg --with-keygrip --list-secret-keys | awk '/Keygrip/{print $3}') ;;
+        esac
+
+        # try to preset the passphrase for each key found
+        for grip in $grips; do
+          "$(gpgconf --list-dirs libexecdir)/gpg-preset-passphrase" -c -P "$2" "$grip"
+        done
+      }
+    '';
+
+    programs.gnupg.agent.enable = true;
+    programs.gnupg.agent.enableSSHSupport = true;
+  };
+
+  testScript =
+    ''
+      import shlex
+
+
+      def as_alice(command: str) -> str:
+          """
+          Wraps a command to run it as Alice in a login shell
+          """
+          quoted = shlex.quote(command)
+          return "su --login alice --command " + quoted
+
+
+      start_all()
+
+      with subtest("Wait for the autologin"):
+          machine.wait_until_tty_matches("1", "alice@machine")
+
+      with subtest("Can generate a PGP key"):
+          # Note: this needs a tty because of pinentry
+          machine.send_chars("gpg --gen-key\n")
+          machine.wait_until_tty_matches("1", "Real name:")
+          machine.send_chars("Alice\n")
+          machine.wait_until_tty_matches("1", "Email address:")
+          machine.send_chars("alice@machine\n")
+          machine.wait_until_tty_matches("1", "Change")
+          machine.send_chars("O\n")
+          machine.wait_until_tty_matches("1", "Please enter")
+          machine.send_chars("pgp_p4ssphrase\n")
+          machine.wait_until_tty_matches("1", "Please re-enter")
+          machine.send_chars("pgp_p4ssphrase\n")
+          machine.wait_until_tty_matches("1", "public and secret key created")
+
+      with subtest("Confirm the key is in the keyring"):
+          machine.wait_until_succeeds(as_alice("gpg --list-secret-keys | grep -q alice@machine"))
+
+      with subtest("Can generate and add an SSH key"):
+          machine.succeed(as_alice("ssh-keygen -t ed25519 -f alice -N ssh_p4ssphrase"))
+
+          # Note: apparently this must be run before using the OpenSSH agent
+          # socket for the first time in a tty. It's not needed for `ssh`
+          # because there's a hook that calls it automatically (only in NixOS).
+          machine.send_chars("gpg-connect-agent updatestartuptty /bye\n")
+
+          # Note: again, this needs a tty because of pinentry
+          machine.send_chars("ssh-add alice\n")
+          machine.wait_until_tty_matches("1", "Enter passphrase")
+          machine.send_chars("ssh_p4ssphrase\n")
+          machine.wait_until_tty_matches("1", "Please enter")
+          machine.send_chars("ssh_agent_p4ssphrase\n")
+          machine.wait_until_tty_matches("1", "Please re-enter")
+          machine.send_chars("ssh_agent_p4ssphrase\n")
+
+      with subtest("Confirm the SSH key has been registered"):
+          machine.wait_until_succeeds(as_alice("ssh-add -l | grep -q alice@machine"))
+
+      with subtest("Can preset the key passphrases in the agent"):
+          machine.succeed(as_alice("echo allow-preset-passphrase > .gnupg/gpg-agent.conf"))
+          machine.succeed(as_alice("pkill gpg-agent"))
+          machine.succeed(as_alice("preset_key pgp pgp_p4ssphrase"))
+          machine.succeed(as_alice("preset_key ssh ssh_agent_p4ssphrase"))
+
+      with subtest("Can encrypt and decrypt a message"):
+          machine.succeed(as_alice("echo Hello | gpg -e -r alice | gpg -d | grep -q Hello"))
+
+      with subtest("Can log into the server"):
+          # Install Alice's public key
+          public_key = machine.succeed(as_alice("cat alice.pub"))
+          server.succeed("mkdir /etc/ssh/authorized_keys.d")
+          server.succeed(f"printf '{public_key}' > /etc/ssh/authorized_keys.d/alice")
+
+          server.wait_for_open_port(22)
+          machine.succeed(as_alice("ssh -i alice -o StrictHostKeyChecking=no server exit"))
+    '';
+})
diff --git a/nixpkgs/nixos/tests/go-camo.nix b/nixpkgs/nixos/tests/go-camo.nix
new file mode 100644
index 000000000000..513964c31c43
--- /dev/null
+++ b/nixpkgs/nixos/tests/go-camo.nix
@@ -0,0 +1,30 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+{
+  gocamo_file_key = let
+      key_val = "12345678";
+    in
+    makeTest {
+    name = "go-camo-file-key";
+    meta = {
+      maintainers = [ pkgs.lib.maintainers.viraptor ];
+    };
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.go-camo = {
+        enable = true;
+        keyFile = pkgs.writeText "foo" key_val;
+      };
+    };
+
+    # go-camo responds to http requests
+    testScript = ''
+      machine.wait_for_unit("go-camo.service")
+      machine.wait_for_open_port(8080)
+      machine.succeed("curl http://localhost:8080")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/go-neb.nix b/nixpkgs/nixos/tests/go-neb.nix
new file mode 100644
index 000000000000..4bd03dcf3c6b
--- /dev/null
+++ b/nixpkgs/nixos/tests/go-neb.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "go-neb";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hexa maralorn ];
+  };
+
+  nodes = {
+    server = {
+      services.go-neb = {
+        enable = true;
+        baseUrl = "http://localhost";
+        secretFile = pkgs.writeText "secrets" "ACCESS_TOKEN=changeme";
+        config = {
+          clients = [ {
+            UserId = "@test:localhost";
+            AccessToken = "$ACCESS_TOKEN";
+            HomeServerUrl = "http://localhost";
+            Sync = false;
+            AutoJoinRooms = false;
+            DisplayName = "neverbeseen";
+          } ];
+          services = [ {
+            ID = "wikipedia_service";
+            Type = "wikipedia";
+            UserID = "@test:localhost";
+            Config = { };
+          } ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("go-neb.service")
+    server.wait_until_succeeds("curl -fL http://localhost:4050/services/hooks/d2lraXBlZGlhX3NlcnZpY2U")
+    server.succeed(
+        "journalctl -eu go-neb -o cat | grep -q service_id=wikipedia_service",
+        "grep -q changeme /var/run/go-neb/config.yaml",
+    )
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/gobgpd.nix b/nixpkgs/nixos/tests/gobgpd.nix
new file mode 100644
index 000000000000..775f65d1199f
--- /dev/null
+++ b/nixpkgs/nixos/tests/gobgpd.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    ifAddr = node: iface: (pkgs.lib.head node.config.networking.interfaces.${iface}.ipv4.addresses).address;
+  in {
+    name = "gobgpd";
+
+    meta = with pkgs.lib.maintainers; { maintainers = [ higebu ]; };
+
+    nodes = {
+      node1 = { nodes, ... }: {
+        environment.systemPackages = [ pkgs.gobgp ];
+        networking.firewall.allowedTCPPorts = [ 179 ];
+        services.gobgpd = {
+          enable = true;
+          settings = {
+            global = {
+              config = {
+                as = 64512;
+                router-id = "192.168.255.1";
+              };
+            };
+            neighbors = [{
+              config = {
+                neighbor-address = ifAddr nodes.node2 "eth1";
+                peer-as = 64513;
+              };
+            }];
+          };
+        };
+      };
+      node2 = { nodes, ... }: {
+        environment.systemPackages = [ pkgs.gobgp ];
+        networking.firewall.allowedTCPPorts = [ 179 ];
+        services.gobgpd = {
+          enable = true;
+          settings = {
+            global = {
+              config = {
+                as = 64513;
+                router-id = "192.168.255.2";
+              };
+            };
+            neighbors = [{
+              config = {
+                neighbor-address = ifAddr nodes.node1 "eth1";
+                peer-as = 64512;
+              };
+            }];
+          };
+        };
+      };
+    };
+
+    testScript = { nodes, ... }: let
+      addr1 = ifAddr nodes.node1 "eth1";
+      addr2 = ifAddr nodes.node2 "eth1";
+    in
+      ''
+      start_all()
+
+      for node in node1, node2:
+          with subtest("should start gobgpd node"):
+              node.wait_for_unit("gobgpd.service")
+          with subtest("should open port 179"):
+              node.wait_for_open_port(179)
+
+      with subtest("should show neighbors by gobgp cli and BGP state should be ESTABLISHED"):
+          node1.wait_until_succeeds("gobgp neighbor ${addr2} | grep -q ESTABLISHED")
+          node2.wait_until_succeeds("gobgp neighbor ${addr1} | grep -q ESTABLISHED")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/gocd-agent.nix b/nixpkgs/nixos/tests/gocd-agent.nix
new file mode 100644
index 000000000000..9301a88ec05d
--- /dev/null
+++ b/nixpkgs/nixos/tests/gocd-agent.nix
@@ -0,0 +1,48 @@
+# verifies:
+#   1. GoCD agent starts
+#   2. GoCD agent responds
+#   3. GoCD agent is available on GoCD server using GoCD API
+#     3.1. https://api.go.cd/current/#get-all-agents
+
+let
+  serverUrl = "localhost:8153/go/api/agents";
+  header = "Accept: application/vnd.go.cd.v2+json";
+in
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "gocd-agent";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ grahamc swarren83 ];
+
+    # gocd agent needs to register with the autoregister key created on first server startup,
+    # but NixOS module doesn't seem to allow to pass during runtime currently
+    broken = true;
+  };
+
+  nodes = {
+    agent =
+      { ... }:
+      {
+        virtualisation.memorySize = 2046;
+        services.gocd-agent = {
+          enable = true;
+        };
+        services.gocd-server = {
+          enable = true;
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    agent.wait_for_unit("gocd-server")
+    agent.wait_for_open_port(8153)
+    agent.wait_for_unit("gocd-agent")
+    agent.wait_until_succeeds(
+        "curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].uuid"
+    )
+    agent.succeed(
+        "curl ${serverUrl} -H '${header}' | ${pkgs.jq}/bin/jq -e ._embedded.agents[0].agent_state | grep Idle"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gocd-server.nix b/nixpkgs/nixos/tests/gocd-server.nix
new file mode 100644
index 000000000000..aff651c5278f
--- /dev/null
+++ b/nixpkgs/nixos/tests/gocd-server.nix
@@ -0,0 +1,28 @@
+# verifies:
+#   1. GoCD server starts
+#   2. GoCD server responds
+
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "gocd-server";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ swarren83 ];
+  };
+
+  nodes = {
+    server =
+      { ... }:
+      {
+        virtualisation.memorySize = 2046;
+        services.gocd-server.enable = true;
+      };
+  };
+
+  testScript = ''
+    server.start()
+    server.wait_for_unit("gocd-server")
+    server.wait_for_open_port(8153)
+    server.wait_until_succeeds("curl -s -f localhost:8153/go")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gollum.nix b/nixpkgs/nixos/tests/gollum.nix
new file mode 100644
index 000000000000..44d373e35262
--- /dev/null
+++ b/nixpkgs/nixos/tests/gollum.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "gollum";
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.gollum.enable = true;
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    webserver.wait_for_unit("gollum")
+    webserver.wait_for_open_port(${toString nodes.webserver.services.gollum.port})
+  '';
+})
diff --git a/nixpkgs/nixos/tests/gonic.nix b/nixpkgs/nixos/tests/gonic.nix
new file mode 100644
index 000000000000..726d7da0970f
--- /dev/null
+++ b/nixpkgs/nixos/tests/gonic.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "gonic";
+
+  nodes.machine = { ... }: {
+    services.gonic = {
+      enable = true;
+      settings = {
+        music-path = [ "/tmp" ];
+        podcast-path = "/tmp";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("gonic")
+    machine.wait_for_open_port(4747)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/google-oslogin/default.nix b/nixpkgs/nixos/tests/google-oslogin/default.nix
new file mode 100644
index 000000000000..cd05af6b9ed7
--- /dev/null
+++ b/nixpkgs/nixos/tests/google-oslogin/default.nix
@@ -0,0 +1,73 @@
+import ../make-test-python.nix ({ pkgs, ... } :
+let
+  inherit (import ./../ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+
+    # don't check host keys or known hosts, use the snakeoil ssh key
+    ssh-config = builtins.toFile "ssh.conf" ''
+      UserKnownHostsFile=/dev/null
+      StrictHostKeyChecking=no
+      IdentityFile=~/.ssh/id_snakeoil
+    '';
+in {
+  name = "google-oslogin";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ flokli ];
+  };
+
+  nodes = {
+    # the server provides both the the mocked google metadata server and the ssh server
+    server = (import ./server.nix pkgs);
+
+    client = { ... }: {};
+  };
+  testScript =  ''
+    MOCKUSER = "mockuser_nixos_org"
+    MOCKADMIN = "mockadmin_nixos_org"
+    start_all()
+
+    server.wait_for_unit("mock-google-metadata.service")
+    server.wait_for_open_port(80)
+
+    # mockserver should return a non-expired ssh key for both mockuser and mockadmin
+    server.succeed(
+        f'${pkgs.google-guest-oslogin}/bin/google_authorized_keys {MOCKUSER} | grep -q "${snakeOilPublicKey}"'
+    )
+    server.succeed(
+        f'${pkgs.google-guest-oslogin}/bin/google_authorized_keys {MOCKADMIN} | grep -q "${snakeOilPublicKey}"'
+    )
+
+    # install snakeoil ssh key on the client, and provision .ssh/config file
+    client.succeed("mkdir -p ~/.ssh")
+    client.succeed(
+        "cat ${snakeOilPrivateKey} > ~/.ssh/id_snakeoil"
+    )
+    client.succeed("chmod 600 ~/.ssh/id_snakeoil")
+    client.succeed("cp ${ssh-config} ~/.ssh/config")
+
+    client.wait_for_unit("network.target")
+    server.wait_for_unit("sshd.service")
+
+    # we should not be able to connect as non-existing user
+    client.fail("ssh ghost@server 'true'")
+
+    # we should be able to connect as mockuser
+    client.succeed(f"ssh {MOCKUSER}@server 'true'")
+    # but we shouldn't be able to sudo
+    client.fail(
+        f"ssh {MOCKUSER}@server '/run/wrappers/bin/sudo /run/current-system/sw/bin/id' | grep -q 'root'"
+    )
+
+    # we should also be able to log in as mockadmin
+    client.succeed(f"ssh {MOCKADMIN}@server 'true'")
+    # pam_oslogin_admin.so should now have generated a sudoers file
+    server.succeed(
+        f"find /run/google-sudoers.d | grep -q '/run/google-sudoers.d/{MOCKADMIN}'"
+    )
+
+    # and we should be able to sudo
+    client.succeed(
+        f"ssh {MOCKADMIN}@server '/run/wrappers/bin/sudo /run/current-system/sw/bin/id' | grep -q 'root'"
+    )
+  '';
+  })
diff --git a/nixpkgs/nixos/tests/google-oslogin/server.nix b/nixpkgs/nixos/tests/google-oslogin/server.nix
new file mode 100644
index 000000000000..3df41155c92d
--- /dev/null
+++ b/nixpkgs/nixos/tests/google-oslogin/server.nix
@@ -0,0 +1,27 @@
+{ pkgs, ... }:
+let
+  inherit (import ./../ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+in {
+  networking.firewall.allowedTCPPorts = [ 80 ];
+
+  systemd.services.mock-google-metadata = {
+    description = "Mock Google metadata service";
+    serviceConfig.Type = "simple";
+    serviceConfig.ExecStart = "${pkgs.python3}/bin/python ${./server.py}";
+    environment = {
+      SNAKEOIL_PUBLIC_KEY = snakeOilPublicKey;
+    };
+    wantedBy = [ "multi-user.target" ];
+    after = [ "network.target" ];
+  };
+
+  services.openssh.enable = true;
+  services.openssh.settings.KbdInteractiveAuthentication = false;
+  services.openssh.settings.PasswordAuthentication = false;
+
+  security.googleOsLogin.enable = true;
+
+  # Mock google service
+  networking.interfaces.lo.ipv4.addresses = [ { address = "169.254.169.254"; prefixLength = 32; } ];
+}
diff --git a/nixpkgs/nixos/tests/google-oslogin/server.py b/nixpkgs/nixos/tests/google-oslogin/server.py
new file mode 100755
index 000000000000..622cd86b2619
--- /dev/null
+++ b/nixpkgs/nixos/tests/google-oslogin/server.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+import json
+import sys
+import time
+import os
+import hashlib
+import base64
+
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from urllib.parse import urlparse, parse_qs
+from typing import Dict
+
+SNAKEOIL_PUBLIC_KEY = os.environ['SNAKEOIL_PUBLIC_KEY']
+MOCKUSER="mockuser_nixos_org"
+MOCKADMIN="mockadmin_nixos_org"
+
+
+def w(msg: bytes):
+    sys.stderr.write(f"{msg}\n")
+    sys.stderr.flush()
+
+
+def gen_fingerprint(pubkey: str):
+    decoded_key = base64.b64decode(pubkey.encode("ascii").split()[1])
+    return hashlib.sha256(decoded_key).hexdigest()
+
+
+def gen_email(username: str):
+    """username seems to be a 21 characters long number string, so mimic that in a reproducible way"""
+    return str(int(hashlib.sha256(username.encode()).hexdigest(), 16))[0:21]
+
+
+def gen_mockuser(username: str, uid: str, gid: str, home_directory: str, snakeoil_pubkey: str) -> Dict:
+    snakeoil_pubkey_fingerprint = gen_fingerprint(snakeoil_pubkey)
+    # seems to be a 21 characters long numberstring, so mimic that in a reproducible way
+    email = gen_email(username)
+    return {
+        "loginProfiles": [
+            {
+                "name": email,
+                "posixAccounts": [
+                    {
+                        "primary": True,
+                        "username": username,
+                        "uid": uid,
+                        "gid": gid,
+                        "homeDirectory": home_directory,
+                        "operatingSystemType": "LINUX"
+                    }
+                ],
+                "sshPublicKeys": {
+                    snakeoil_pubkey_fingerprint: {
+                        "key": snakeoil_pubkey,
+                        "expirationTimeUsec": str((time.time() + 600) * 1000000),  # 10 minutes in the future
+                        "fingerprint": snakeoil_pubkey_fingerprint
+                    }
+                }
+            }
+        ]
+    }
+
+
+class ReqHandler(BaseHTTPRequestHandler):
+
+    def _send_json_ok(self, data: dict):
+        self.send_response(200)
+        self.send_header('Content-type', 'application/json')
+        self.end_headers()
+        out = json.dumps(data).encode()
+        w(out)
+        self.wfile.write(out)
+
+    def _send_json_success(self, success=True):
+        self.send_response(200)
+        self.send_header('Content-type', 'application/json')
+        self.end_headers()
+        out = json.dumps({"success": success}).encode()
+        w(out)
+        self.wfile.write(out)
+
+    def _send_404(self):
+        self.send_response(404)
+        self.end_headers()
+
+    def do_GET(self):
+        p = str(self.path)
+        pu = urlparse(p)
+        params = parse_qs(pu.query)
+
+        # users endpoint
+        if pu.path == "/computeMetadata/v1/oslogin/users":
+            # mockuser and mockadmin are allowed to login, both use the same snakeoil public key
+            if params.get('username') == [MOCKUSER] or params.get('uid') == ["1009719690"]:
+                username = MOCKUSER
+                uid = "1009719690"
+            elif params.get('username') == [MOCKADMIN] or params.get('uid') == ["1009719691"]:
+                username = MOCKADMIN
+                uid = "1009719691"
+            else:
+                self._send_404()
+                return
+
+            self._send_json_ok(gen_mockuser(username=username, uid=uid, gid=uid, home_directory=f"/home/{username}", snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY))
+            return
+
+        # we need to provide something at the groups endpoint.
+        # the nss module does segfault if we don't.
+        elif pu.path == "/computeMetadata/v1/oslogin/groups":
+            self._send_json_ok({
+                "posixGroups": [
+                    {"name" : "demo", "gid" : 4294967295}
+                ],
+            })
+            return
+
+        # authorize endpoint
+        elif pu.path == "/computeMetadata/v1/oslogin/authorize":
+            # is user allowed to login?
+            if params.get("policy") == ["login"]:
+                # mockuser and mockadmin are allowed to login
+                if params.get('email') == [gen_email(MOCKUSER)] or params.get('email') == [gen_email(MOCKADMIN)]:
+                    self._send_json_success()
+                    return
+                self._send_json_success(False)
+                return
+            # is user allowed to become root?
+            elif params.get("policy") == ["adminLogin"]:
+                # only mockadmin is allowed to become admin
+                self._send_json_success((params['email'] == [gen_email(MOCKADMIN)]))
+                return
+            # send 404 for other policies
+            else:
+                self._send_404()
+                return
+        else:
+            sys.stderr.write(f"Unhandled path: {p}\n")
+            sys.stderr.flush()
+            self.send_response(404)
+            self.end_headers()
+            self.wfile.write(b'')
+
+
+if __name__ == '__main__':
+    s = HTTPServer(('0.0.0.0', 80), ReqHandler)
+    s.serve_forever()
diff --git a/nixpkgs/nixos/tests/goss.nix b/nixpkgs/nixos/tests/goss.nix
new file mode 100644
index 000000000000..6b772d19215e
--- /dev/null
+++ b/nixpkgs/nixos/tests/goss.nix
@@ -0,0 +1,53 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "goss";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine = {
+    environment.systemPackages = [ pkgs.jq ];
+
+    services.goss = {
+      enable = true;
+
+      environment = {
+        GOSS_FMT = "json";
+      };
+
+      settings = {
+        addr."tcp://localhost:8080" = {
+          reachable = true;
+          local-address = "127.0.0.1";
+        };
+        command."check-goss-version" = {
+          exec = "${lib.getExe pkgs.goss} --version";
+          exit-status = 0;
+        };
+        dns.localhost.resolvable = true;
+        file."/nix" = {
+          filetype = "directory";
+          exists = true;
+        };
+        group.root.exists = true;
+        kernel-param."kernel.ostype".value = "Linux";
+        service.goss = {
+          enabled = true;
+          running = true;
+        };
+        user.root.exists = true;
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    machine.wait_for_unit("goss.service")
+    machine.wait_for_open_port(8080)
+
+    with subtest("returns health status"):
+      result = json.loads(machine.succeed("curl -sS http://localhost:8080/healthz"))
+
+      assert len(result["results"]) == 10, f".results should be an array of 10 items, was {result['results']!r}"
+      assert result["summary"]["failed-count"] == 0, f".summary.failed-count should be zero, was {result['summary']['failed-count']}"
+      assert result["summary"]["test-count"] == 10, f".summary.test-count should be 10, was {result['summary']['test-count']}"
+    '';
+})
diff --git a/nixpkgs/nixos/tests/gotify-server.nix b/nixpkgs/nixos/tests/gotify-server.nix
new file mode 100644
index 000000000000..c8d7fa172a7b
--- /dev/null
+++ b/nixpkgs/nixos/tests/gotify-server.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "gotify-server";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.jq ];
+
+    services.gotify = {
+      enable = true;
+      port = 3000;
+    };
+  };
+
+  testScript = ''
+    machine.start()
+
+    machine.wait_for_unit("gotify-server.service")
+    machine.wait_for_open_port(3000)
+
+    token = machine.succeed(
+        "curl --fail -sS -X POST localhost:3000/application -F name=nixos "
+        + '-H "Authorization: Basic $(echo -ne "admin:admin" | base64 --wrap 0)" '
+        + "| jq .token | xargs echo -n"
+    )
+
+    usertoken = machine.succeed(
+        "curl --fail -sS -X POST localhost:3000/client -F name=nixos "
+        + '-H "Authorization: Basic $(echo -ne "admin:admin" | base64 --wrap 0)" '
+        + "| jq .token | xargs echo -n"
+    )
+
+    machine.succeed(
+        f"curl --fail -sS -X POST 'localhost:3000/message?token={token}' -H 'Accept: application/json' "
+        + "-F title=Gotify -F message=Works"
+    )
+
+    title = machine.succeed(
+        f"curl --fail -sS 'localhost:3000/message?since=0&token={usertoken}' | jq '.messages|.[0]|.title' | xargs echo -n"
+    )
+
+    assert title == "Gotify"
+
+    # Ensure that the UI responds with a successful code and that the
+    # response is not empty
+    result = machine.succeed("curl -fsS localhost:3000")
+    assert result, "HTTP response from localhost:3000 must not be empty!"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/grafana-agent.nix b/nixpkgs/nixos/tests/grafana-agent.nix
new file mode 100644
index 000000000000..a9f34d8cea31
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana-agent.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+  let
+    nodes = {
+      machine = {
+        services.grafana-agent = {
+          enable = true;
+        };
+      };
+    };
+  in
+  {
+    name = "grafana-agent";
+
+    meta = with lib.maintainers; {
+      maintainers = [ zimbatm ];
+    };
+
+    inherit nodes;
+
+    testScript = ''
+      start_all()
+
+      with subtest("Grafana-agent is running"):
+          machine.wait_for_unit("grafana-agent.service")
+          machine.wait_for_open_port(12345)
+          machine.succeed(
+              "curl -sSfN http://127.0.0.1:12345/-/healthy"
+          )
+          machine.shutdown()
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/grafana/basic.nix b/nixpkgs/nixos/tests/grafana/basic.nix
new file mode 100644
index 000000000000..dd389bc8a3d1
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/basic.nix
@@ -0,0 +1,142 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  inherit (lib) mkMerge nameValuePair maintainers;
+
+  baseGrafanaConf = {
+    services.grafana = {
+      enable = true;
+      settings = {
+        analytics.reporting_enabled = false;
+
+        server = {
+          http_addr = "localhost";
+          domain = "localhost";
+        };
+
+        security = {
+          admin_user = "testadmin";
+          admin_password = "snakeoilpwd";
+        };
+      };
+    };
+  };
+
+  extraNodeConfs = {
+    sqlite = {};
+
+    socket = { config, ... }: {
+      services.grafana.settings.server = {
+        protocol = "socket";
+        socket = "/run/grafana/sock";
+        socket_gid = config.users.groups.nginx.gid;
+      };
+
+      users.users.grafana.extraGroups = [ "nginx" ];
+
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+        virtualHosts."_".locations."/".proxyPass = "http://unix:/run/grafana/sock";
+      };
+    };
+
+    declarativePlugins = {
+      services.grafana.declarativePlugins = [ pkgs.grafanaPlugins.grafana-clock-panel ];
+    };
+
+    postgresql = {
+      services.grafana.settings.database = {
+        host = "127.0.0.1:5432";
+        user = "grafana";
+      };
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "grafana" ];
+        ensureUsers = [{
+          name = "grafana";
+          ensureDBOwnership = true;
+        }];
+      };
+      systemd.services.grafana.after = [ "postgresql.service" ];
+    };
+
+    mysql = {
+      services.grafana.settings.database.user = "grafana";
+      services.mysql = {
+        enable = true;
+        ensureDatabases = [ "grafana" ];
+        ensureUsers = [{
+          name = "grafana";
+          ensurePermissions."grafana.*" = "ALL PRIVILEGES";
+        }];
+        package = pkgs.mariadb;
+      };
+      systemd.services.grafana.after = [ "mysql.service" ];
+    };
+  };
+
+  nodes = builtins.mapAttrs (_: val: mkMerge [ val baseGrafanaConf ]) extraNodeConfs;
+in {
+  name = "grafana-basic";
+
+  meta = with maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  inherit nodes;
+
+  testScript = ''
+    start_all()
+
+    with subtest("Declarative plugins installed"):
+        declarativePlugins.wait_for_unit("grafana.service")
+        declarativePlugins.wait_for_open_port(3000)
+        declarativePlugins.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/plugins | grep grafana-clock-panel"
+        )
+        declarativePlugins.shutdown()
+
+    with subtest("Successful API query as admin user with sqlite db"):
+        sqlite.wait_for_unit("grafana.service")
+        sqlite.wait_for_open_port(3000)
+        print(sqlite.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users -i"
+        ))
+        sqlite.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep admin\@localhost"
+        )
+        sqlite.shutdown()
+
+    with subtest("Successful API query as admin user with sqlite db listening on socket"):
+        socket.wait_for_unit("grafana.service")
+        socket.wait_for_open_port(80)
+        print(socket.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1/api/org/users -i"
+        ))
+        socket.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1/api/org/users | grep admin\@localhost"
+        )
+        socket.shutdown()
+
+    with subtest("Successful API query as admin user with postgresql db"):
+        postgresql.wait_for_unit("grafana.service")
+        postgresql.wait_for_unit("postgresql.service")
+        postgresql.wait_for_open_port(3000)
+        postgresql.wait_for_open_port(5432)
+        postgresql.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep admin\@localhost"
+        )
+        postgresql.shutdown()
+
+    with subtest("Successful API query as admin user with mysql db"):
+        mysql.wait_for_unit("grafana.service")
+        mysql.wait_for_unit("mysql.service")
+        mysql.wait_for_open_port(3000)
+        mysql.wait_for_open_port(3306)
+        mysql.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep admin\@localhost"
+        )
+        mysql.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/grafana/default.nix b/nixpkgs/nixos/tests/grafana/default.nix
new file mode 100644
index 000000000000..9c2622571800
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/default.nix
@@ -0,0 +1,9 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+
+{
+  basic = import ./basic.nix { inherit system pkgs; };
+  provision = import ./provision { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/grafana/provision/contact-points.yaml b/nixpkgs/nixos/tests/grafana/provision/contact-points.yaml
new file mode 100644
index 000000000000..2a5f14e75e2d
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/contact-points.yaml
@@ -0,0 +1,9 @@
+apiVersion: 1
+
+contactPoints:
+  - name: "Test Contact Point"
+    receivers:
+      - uid: "test_contact_point"
+        type: prometheus-alertmanager
+        settings:
+          url: http://localhost:9000
diff --git a/nixpkgs/nixos/tests/grafana/provision/dashboards.yaml b/nixpkgs/nixos/tests/grafana/provision/dashboards.yaml
new file mode 100644
index 000000000000..dc83fe6b892d
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/dashboards.yaml
@@ -0,0 +1,6 @@
+apiVersion: 1
+
+providers:
+  - name: 'default'
+    options:
+      path: /var/lib/grafana/dashboards
diff --git a/nixpkgs/nixos/tests/grafana/provision/datasources.yaml b/nixpkgs/nixos/tests/grafana/provision/datasources.yaml
new file mode 100644
index 000000000000..ccf9481db7f3
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/datasources.yaml
@@ -0,0 +1,7 @@
+apiVersion: 1
+
+datasources:
+  - name: 'Test Datasource'
+    type: 'testdata'
+    access: 'proxy'
+    uid: 'test_datasource'
diff --git a/nixpkgs/nixos/tests/grafana/provision/default.nix b/nixpkgs/nixos/tests/grafana/provision/default.nix
new file mode 100644
index 000000000000..d33d16ce1209
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/default.nix
@@ -0,0 +1,256 @@
+import ../../make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  inherit (lib) mkMerge nameValuePair maintainers;
+
+  baseGrafanaConf = {
+    services.grafana = {
+      enable = true;
+      provision.enable = true;
+      settings = {
+        analytics.reporting_enabled = false;
+
+        server = {
+          http_addr = "localhost";
+          domain = "localhost";
+        };
+
+        security = {
+          admin_user = "testadmin";
+          admin_password = "$__file{${pkgs.writeText "pwd" "snakeoilpwd"}}";
+        };
+      };
+    };
+
+    systemd.tmpfiles.rules =
+      let
+        dashboard = pkgs.writeText "test.json" (builtins.readFile ./test_dashboard.json);
+      in
+      [
+        "d /var/lib/grafana/dashboards 0700 grafana grafana -"
+        "C+ /var/lib/grafana/dashboards/test.json - - - - ${dashboard}"
+      ];
+  };
+
+  extraNodeConfs = {
+    provisionLegacyNotifiers = {
+      services.grafana.provision = {
+        datasources.settings = {
+          apiVersion = 1;
+          datasources = [{
+            name = "Test Datasource";
+            type = "testdata";
+            access = "proxy";
+            uid = "test_datasource";
+          }];
+        };
+        dashboards.settings = {
+          apiVersion = 1;
+          providers = [{
+            name = "default";
+            options.path = "/var/lib/grafana/dashboards";
+          }];
+        };
+        notifiers = [{
+          uid = "test_notifiers";
+          name = "Test Notifiers";
+          type = "email";
+          settings = {
+            singleEmail = true;
+            addresses = "test@test.com";
+          };
+        }];
+      };
+    };
+    provisionNix = {
+      services.grafana.provision = {
+        datasources.settings = {
+          apiVersion = 1;
+          datasources = [{
+            name = "Test Datasource";
+            type = "testdata";
+            access = "proxy";
+            uid = "test_datasource";
+          }];
+        };
+
+        dashboards.settings = {
+          apiVersion = 1;
+          providers = [{
+            name = "default";
+            options.path = "/var/lib/grafana/dashboards";
+          }];
+        };
+
+        alerting = {
+          rules.settings = {
+            groups = [{
+              name = "test_rule_group";
+              folder = "test_folder";
+              interval = "60s";
+              rules = [{
+                uid = "test_rule";
+                title = "Test Rule";
+                condition = "A";
+                data = [{
+                  refId = "A";
+                  datasourceUid = "-100";
+                  model = {
+                    conditions = [{
+                      evaluator = {
+                        params = [ 3 ];
+                        type = "git";
+                      };
+                      operator.type = "and";
+                      query.params = [ "A" ];
+                      reducer.type = "last";
+                      type = "query";
+                    }];
+                    datasource = {
+                      type = "__expr__";
+                      uid = "-100";
+                    };
+                    expression = "1==0";
+                    intervalMs = 1000;
+                    maxDataPoints = 43200;
+                    refId = "A";
+                    type = "math";
+                  };
+                }];
+                for = "60s";
+              }];
+            }];
+          };
+
+          contactPoints.settings = {
+            contactPoints = [{
+              name = "Test Contact Point";
+              receivers = [{
+                uid = "test_contact_point";
+                type = "prometheus-alertmanager";
+                settings.url = "http://localhost:9000";
+              }];
+            }];
+          };
+
+          policies.settings = {
+            policies = [{
+              receiver = "Test Contact Point";
+            }];
+          };
+
+          templates.settings = {
+            templates = [{
+              name = "Test Template";
+              template = "Test message";
+            }];
+          };
+
+          muteTimings.settings = {
+            muteTimes = [{
+              name = "Test Mute Timing";
+            }];
+          };
+        };
+      };
+    };
+
+    provisionYaml = {
+      services.grafana.provision = {
+        datasources.path = ./datasources.yaml;
+        dashboards.path = ./dashboards.yaml;
+        alerting = {
+          rules.path = ./rules.yaml;
+          contactPoints.path = ./contact-points.yaml;
+          policies.path = ./policies.yaml;
+          templates.path = ./templates.yaml;
+          muteTimings.path = ./mute-timings.yaml;
+        };
+      };
+    };
+
+    provisionYamlDirs = let
+      mkdir = p: pkgs.writeTextDir (baseNameOf p) (builtins.readFile p);
+    in {
+      services.grafana.provision = {
+        datasources.path = mkdir ./datasources.yaml;
+        dashboards.path = mkdir ./dashboards.yaml;
+        alerting = {
+          rules.path = mkdir ./rules.yaml;
+          contactPoints.path = mkdir ./contact-points.yaml;
+          policies.path = mkdir ./policies.yaml;
+          templates.path = mkdir ./templates.yaml;
+          muteTimings.path = mkdir ./mute-timings.yaml;
+        };
+      };
+    };
+  };
+
+  nodes = builtins.mapAttrs (_: val: mkMerge [ val baseGrafanaConf ]) extraNodeConfs;
+in {
+  name = "grafana-provision";
+
+  meta = with maintainers; {
+    maintainers = [ kfears willibutz ];
+  };
+
+  inherit nodes;
+
+  testScript = ''
+    start_all()
+
+    nodeNix = ("Nix (new format)", provisionNix)
+    nodeYaml = ("Nix (YAML)", provisionYaml)
+    nodeYamlDir = ("Nix (YAML in dirs)", provisionYamlDirs)
+
+    for description, machine in [nodeNix, nodeYaml, nodeYamlDir]:
+        with subtest(f"Should start provision node: {description}"):
+            machine.wait_for_unit("grafana.service")
+            machine.wait_for_open_port(3000)
+
+        with subtest(f"Successful datasource provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/datasources/uid/test_datasource | grep Test\ Datasource"
+            )
+
+        with subtest(f"Successful dashboard provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/dashboards/uid/test_dashboard | grep Test\ Dashboard"
+            )
+
+        with subtest(f"Successful rule provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/alert-rules/test_rule | grep Test\ Rule"
+            )
+
+        with subtest(f"Successful contact point provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point"
+            )
+
+        with subtest(f"Successful policy provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/policies | grep Test\ Contact\ Point"
+            )
+
+        with subtest(f"Successful template provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/templates | grep Test\ Template"
+            )
+
+        with subtest("Successful mute timings provision with {description}"):
+            machine.succeed(
+                "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/mute-timings | grep Test\ Mute\ Timing"
+            )
+
+    with subtest("Successful notifiers provision"):
+        provisionLegacyNotifiers.wait_for_unit("grafana.service")
+        provisionLegacyNotifiers.wait_for_open_port(3000)
+        print(provisionLegacyNotifiers.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers"
+        ))
+        provisionLegacyNotifiers.succeed(
+            "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers | grep Test\ Notifiers"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/grafana/provision/mute-timings.yaml b/nixpkgs/nixos/tests/grafana/provision/mute-timings.yaml
new file mode 100644
index 000000000000..1f47f7c18f0c
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/mute-timings.yaml
@@ -0,0 +1,4 @@
+apiVersion: 1
+
+muteTimes:
+  - name: "Test Mute Timing"
diff --git a/nixpkgs/nixos/tests/grafana/provision/policies.yaml b/nixpkgs/nixos/tests/grafana/provision/policies.yaml
new file mode 100644
index 000000000000..eb31126c4ba5
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/policies.yaml
@@ -0,0 +1,4 @@
+apiVersion: 1
+
+policies:
+  - receiver: "Test Contact Point"
diff --git a/nixpkgs/nixos/tests/grafana/provision/rules.yaml b/nixpkgs/nixos/tests/grafana/provision/rules.yaml
new file mode 100644
index 000000000000..946539c8cb69
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/rules.yaml
@@ -0,0 +1,36 @@
+apiVersion: 1
+
+groups:
+  - name: "test_rule_group"
+    folder: "test_group"
+    interval: 60s
+    rules:
+      - uid: "test_rule"
+        title: "Test Rule"
+        condition: A
+        data:
+          - refId: A
+            datasourceUid: '-100'
+            model:
+              conditions:
+                - evaluator:
+                    params:
+                      - 3
+                    type: gt
+                  operator:
+                    type: and
+                  query:
+                    params:
+                      - A
+                  reducer:
+                    type: last
+                  type: query
+              datasource:
+                type: __expr__
+                uid: '-100'
+              expression: 1==0
+              intervalMs: 1000
+              maxDataPoints: 43200
+              refId: A
+              type: math
+        for: 60s
diff --git a/nixpkgs/nixos/tests/grafana/provision/templates.yaml b/nixpkgs/nixos/tests/grafana/provision/templates.yaml
new file mode 100644
index 000000000000..09df247b3451
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/templates.yaml
@@ -0,0 +1,5 @@
+apiVersion: 1
+
+templates:
+  - name: "Test Template"
+    template: "Test message"
diff --git a/nixpkgs/nixos/tests/grafana/provision/test_dashboard.json b/nixpkgs/nixos/tests/grafana/provision/test_dashboard.json
new file mode 100644
index 000000000000..6e7a5b37f22b
--- /dev/null
+++ b/nixpkgs/nixos/tests/grafana/provision/test_dashboard.json
@@ -0,0 +1,47 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": {
+          "type": "grafana",
+          "uid": "-- Grafana --"
+        },
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "target": {
+          "limit": 100,
+          "matchAny": false,
+          "tags": [],
+          "type": "dashboard"
+        },
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "fiscalYearStartMonth": 0,
+  "graphTooltip": 0,
+  "id": 28,
+  "links": [],
+  "liveNow": false,
+  "panels": [],
+  "schemaVersion": 37,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": []
+  },
+  "time": {
+    "from": "now-6h",
+    "to": "now"
+  },
+  "timepicker": {},
+  "timezone": "",
+  "title": "Test Dashboard",
+  "uid": "test_dashboard",
+  "version": 1,
+  "weekStart": ""
+}
diff --git a/nixpkgs/nixos/tests/graphite.nix b/nixpkgs/nixos/tests/graphite.nix
new file mode 100644
index 000000000000..de6cd8a50e17
--- /dev/null
+++ b/nixpkgs/nixos/tests/graphite.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ... } :
+{
+  name = "graphite";
+  nodes = {
+    one =
+      { ... }: {
+        time.timeZone = "UTC";
+        services.graphite = {
+          web = {
+            enable = true;
+            extraConfig = ''
+              SECRET_KEY = "abcd";
+            '';
+          };
+          carbon.enableCache = true;
+          seyren.enable = false;  # Implicitly requires openssl-1.0.2u which is marked insecure
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    one.wait_for_unit("default.target")
+    one.wait_for_unit("graphiteWeb.service")
+    one.wait_for_unit("carbonCache.service")
+    # The services above are of type "simple". systemd considers them active immediately
+    # even if they're still in preStart (which takes quite long for graphiteWeb).
+    # Wait for ports to open so we're sure the services are up and listening.
+    one.wait_for_open_port(8080)
+    one.wait_for_open_port(2003)
+    one.succeed('echo "foo 1 `date +%s`" | nc -N localhost 2003')
+    one.wait_until_succeeds(
+        "curl 'http://localhost:8080/metrics/find/?query=foo&format=treejson' --silent | grep foo >&2"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/graylog.nix b/nixpkgs/nixos/tests/graylog.nix
new file mode 100644
index 000000000000..3f7cc3a91439
--- /dev/null
+++ b/nixpkgs/nixos/tests/graylog.nix
@@ -0,0 +1,114 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "graylog";
+  meta.maintainers = with lib.maintainers; [ ];
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.memorySize = 4096;
+    virtualisation.diskSize = 4096;
+
+    services.mongodb.enable = true;
+    services.elasticsearch.enable = true;
+    services.elasticsearch.extraConf = ''
+      network.publish_host: 127.0.0.1
+      network.bind_host: 127.0.0.1
+    '';
+
+    services.graylog = {
+      enable = true;
+      passwordSecret = "YGhZ59wXMrYOojx5xdgEpBpDw2N6FbhM4lTtaJ1KPxxmKrUvSlDbtWArwAWMQ5LKx1ojHEVrQrBMVRdXbRyZLqffoUzHfssc";
+      elasticsearchHosts = [ "http://localhost:9200" ];
+
+      # `echo -n "nixos" | shasum -a 256`
+      rootPasswordSha2 = "6ed332bcfa615381511d4d5ba44a293bb476f368f7e9e304f0dff50230d1a85b";
+    };
+
+    environment.systemPackages = [ pkgs.jq ];
+
+    systemd.services.graylog.path = [ pkgs.netcat ];
+    systemd.services.graylog.preStart = ''
+      until nc -z localhost 9200; do
+        sleep 2
+      done
+    '';
+  };
+
+  testScript = let
+    payloads.login = pkgs.writeText "login.json" (builtins.toJSON {
+      host = "127.0.0.1:9000";
+      username = "admin";
+      password = "nixos";
+    });
+
+    payloads.input = pkgs.writeText "input.json" (builtins.toJSON {
+      title = "Demo";
+      global = false;
+      type = "org.graylog2.inputs.gelf.udp.GELFUDPInput";
+      node = "@node@";
+      configuration = {
+        bind_address = "0.0.0.0";
+        decompress_size_limit = 8388608;
+        number_worker_threads = 1;
+        override_source = null;
+        port = 12201;
+        recv_buffer_size = 262144;
+      };
+    });
+
+    payloads.gelf_message = pkgs.writeText "gelf.json" (builtins.toJSON {
+      host = "example.org";
+      short_message = "A short message";
+      full_message = "A long message";
+      version = "1.1";
+      level = 5;
+      facility = "Test";
+    });
+  in ''
+    machine.start()
+    machine.wait_for_unit("graylog.service")
+    machine.wait_for_open_port(9000)
+    machine.succeed("curl -sSfL http://127.0.0.1:9000/")
+
+    session = machine.succeed(
+        "curl -X POST "
+        + "-sSfL http://127.0.0.1:9000/api/system/sessions "
+        + "-d $(cat ${payloads.login}) "
+        + "-H 'Content-Type: application/json' "
+        + "-H 'Accept: application/json' "
+        + "-H 'x-requested-by: cli' "
+        + "| jq .session_id | xargs echo"
+    ).rstrip()
+
+    machine.succeed(
+        "curl -X POST "
+        + f"-sSfL http://127.0.0.1:9000/api/system/inputs -u {session}:session "
+        + '-d $(cat ${payloads.input} | sed -e "s,@node@,$(cat /var/lib/graylog/server/node-id),") '
+        + "-H 'Accept: application/json' "
+        + "-H 'Content-Type: application/json' "
+        + "-H 'x-requested-by: cli' "
+    )
+
+    machine.wait_until_succeeds(
+        "test \"$(curl -sSfL 'http://127.0.0.1:9000/api/cluster/inputstates' "
+        + f"-u {session}:session "
+        + "-H 'Accept: application/json' "
+        + "-H 'Content-Type: application/json' "
+        + "-H 'x-requested-by: cli'"
+        + "| jq 'to_entries[]|.value|.[0]|.state' | xargs echo"
+        + ')" = "RUNNING"'
+    )
+
+    machine.succeed(
+        "echo -n $(cat ${payloads.gelf_message}) | nc -w10 -u 127.0.0.1 12201"
+    )
+
+    machine.succeed(
+        'test "$(curl -X GET '
+        + "-sSfL 'http://127.0.0.1:9000/api/search/universal/relative?query=*' "
+        + f"-u {session}:session "
+        + "-H 'Accept: application/json' "
+        + "-H 'Content-Type: application/json' "
+        + "-H 'x-requested-by: cli'"
+        + ' | jq \'.total_results\' | xargs echo)" = "1"'
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/grocy.nix b/nixpkgs/nixos/tests/grocy.nix
new file mode 100644
index 000000000000..48bbc9f7d3fa
--- /dev/null
+++ b/nixpkgs/nixos/tests/grocy.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "grocy";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    services.grocy = {
+      enable = true;
+      hostName = "localhost";
+      nginx.enableSSL = false;
+    };
+    environment.systemPackages = [ pkgs.jq ];
+  };
+
+  testScript = ''
+    from base64 import b64encode
+    from urllib.parse import quote
+
+    machine.start()
+    machine.wait_for_open_port(80)
+    machine.wait_for_unit("multi-user.target")
+
+    machine.succeed("curl -sSf http://localhost")
+
+    machine.succeed(
+        "curl -c cookies -sSf -X POST http://localhost/login -d 'username=admin&password=admin'"
+    )
+
+    cookie = machine.succeed(
+        "grep -v '^#' cookies | awk '{ print $7 }' | sed -e '/^$/d' | perl -pe 'chomp'"
+    )
+
+    machine.succeed(
+        f"curl -sSf -X POST http://localhost/api/objects/tasks -b 'grocy_session={cookie}' "
+        + '-d \'{"assigned_to_user_id":1,"name":"Test Task","due_date":"1970-01-01"}\'''
+        + " --header 'Content-Type: application/json'"
+    )
+
+    task_name = machine.succeed(
+        f"curl -sSf http://localhost/api/tasks -b 'grocy_session={cookie}' --header 'Accept: application/json' | jq '.[].name' | xargs echo | perl -pe 'chomp'"
+    )
+
+    assert task_name == "Test Task"
+
+    machine.succeed("curl -sSI http://localhost/api/tasks 2>&1 | grep '401 Unauthorized'")
+
+    file_name = "test.txt"
+    file_name_base64 = b64encode(file_name.encode('ascii')).decode('ascii')
+    file_name_base64_urlencode = quote(file_name_base64)
+
+    machine.succeed(
+        f"echo Sample equipment manual > /tmp/{file_name}"
+    )
+
+    machine.succeed(
+        f"curl -sSf -X 'PUT' -b 'grocy_session={cookie}' "
+        + f" 'http://localhost/api/files/equipmentmanuals/{file_name_base64_urlencode}' "
+        + "  --header 'Accept: */*' "
+        + "  --header 'Content-Type: application/octet-stream' "
+        + f" --data-binary '@/tmp/{file_name}' "
+    )
+
+    machine.succeed(
+        f"curl -sSf -X 'GET' -b 'grocy_session={cookie}' "
+        + f" 'http://localhost/api/files/equipmentmanuals/{file_name_base64_urlencode}' "
+        + "  --header 'Accept: application/octet-stream' "
+        + f" | cmp /tmp/{file_name}"
+    )
+
+    machine.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/grow-partition.nix b/nixpkgs/nixos/tests/grow-partition.nix
new file mode 100644
index 000000000000..344910848dca
--- /dev/null
+++ b/nixpkgs/nixos/tests/grow-partition.nix
@@ -0,0 +1,83 @@
+{ lib, ... }:
+
+let
+  rootFslabel = "external";
+  rootFsDevice = "/dev/disk/by-label/${rootFslabel}";
+
+  externalModule = partitionTableType: { config, lib, pkgs, ... }: {
+    virtualisation.directBoot.enable = false;
+    virtualisation.mountHostNixStore = false;
+    virtualisation.useEFIBoot = partitionTableType == "efi";
+
+    # This stops the qemu-vm module from overriding the fileSystems option
+    # with virtualisation.fileSystems.
+    virtualisation.fileSystems = lib.mkForce { };
+
+
+    boot.loader.grub.enable = true;
+    boot.loader.grub.efiSupport = partitionTableType == "efi";
+    boot.loader.grub.efiInstallAsRemovable = partitionTableType == "efi";
+    boot.loader.grub.device = if partitionTableType == "efi" then "nodev" else "/dev/vda";
+
+    boot.growPartition = true;
+
+    fileSystems = {
+      "/".device = rootFsDevice;
+    };
+
+    system.build.diskImage = import ../lib/make-disk-image.nix {
+      inherit config lib pkgs;
+      label = rootFslabel;
+      inherit partitionTableType;
+      format = "raw";
+      bootSize = "128M";
+      additionalSpace = "0M";
+      copyChannel = false;
+    };
+  };
+in
+{
+  name = "grow-partition";
+
+  meta.maintainers = with lib.maintainers; [ arianvp ];
+
+  nodes = {
+    efi = externalModule "efi";
+    legacy = externalModule "legacy";
+    legacyGPT = externalModule "legacy+gpt";
+    hybrid = externalModule "hybrid";
+  };
+
+
+  testScript = { nodes, ... }:
+    lib.concatLines (lib.mapAttrsToList (name: node: ''
+    import os
+    import subprocess
+    import tempfile
+    import shutil
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    shutil.copyfile("${node.system.build.diskImage}/nixos.img", tmp_disk_image.name)
+
+    subprocess.run([
+      "${node.virtualisation.qemu.package}/bin/qemu-img",
+      "resize",
+      "-f",
+      "raw",
+      tmp_disk_image.name,
+      "+32M",
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+
+    ${name}.wait_for_unit("growpart.service")
+    systemd_growpart_logs = ${name}.succeed("journalctl --boot --unit growpart.service")
+    assert "CHANGED" in systemd_growpart_logs
+    ${name}.succeed("systemctl restart growpart.service")
+    systemd_growpart_logs = ${name}.succeed("journalctl --boot --unit growpart.service")
+    assert "NOCHANGE" in systemd_growpart_logs
+
+    '') nodes);
+}
diff --git a/nixpkgs/nixos/tests/grub.nix b/nixpkgs/nixos/tests/grub.nix
new file mode 100644
index 000000000000..e0875e70f6a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/grub.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "grub";
+
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  nodes.machine = { ... }: {
+    virtualisation.useBootLoader = true;
+
+    boot.loader.timeout = null;
+    boot.loader.grub = {
+      enable = true;
+      users.alice.password = "supersecret";
+
+      # OCR is not accurate enough
+      extraConfig = "serial; terminal_output serial";
+    };
+  };
+
+  testScript = ''
+    def grub_login_as(user, password):
+        """
+        Enters user and password to log into GRUB
+        """
+        machine.wait_for_console_text("Enter username:")
+        machine.send_chars(user + "\n")
+        machine.wait_for_console_text("Enter password:")
+        machine.send_chars(password + "\n")
+
+
+    def grub_select_all_configurations():
+        """
+        Selects "All configurations" from the GRUB menu
+        to trigger a login request.
+        """
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey ret")
+
+
+    machine.start()
+
+    # wait for grub screen
+    machine.wait_for_console_text("GNU GRUB")
+
+    grub_select_all_configurations()
+    with subtest("Invalid credentials are rejected"):
+        grub_login_as("wronguser", "wrongsecret")
+        machine.wait_for_console_text("error: access denied.")
+
+    grub_select_all_configurations()
+    with subtest("Valid credentials are accepted"):
+        grub_login_as("alice", "supersecret")
+        machine.send_chars("\n")  # press enter to boot
+        machine.wait_for_console_text("Linux version")
+
+    with subtest("Machine boots correctly"):
+        machine.wait_for_unit("multi-user.target")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/guacamole-server.nix b/nixpkgs/nixos/tests/guacamole-server.nix
new file mode 100644
index 000000000000..48194fddfb22
--- /dev/null
+++ b/nixpkgs/nixos/tests/guacamole-server.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+{
+  name = "guacamole-server";
+
+  nodes = {
+    machine = {pkgs, ...}: {
+      services.guacamole-server = {
+        enable = true;
+        host = "0.0.0.0";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("guacamole-server.service")
+    machine.wait_for_open_port(4822)
+  '';
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+})
diff --git a/nixpkgs/nixos/tests/guix/basic.nix b/nixpkgs/nixos/tests/guix/basic.nix
new file mode 100644
index 000000000000..9b943b8965e6
--- /dev/null
+++ b/nixpkgs/nixos/tests/guix/basic.nix
@@ -0,0 +1,42 @@
+# Take note the Guix store directory is empty. Also, we're trying to prevent
+# Guix from trying to downloading substitutes because of the restricted
+# access (assuming it's in a sandboxed environment).
+#
+# So this test is what it is: a basic test while trying to use Guix as much as
+# we possibly can (including the API) without triggering its download alarm.
+
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "guix-basic";
+  meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
+
+  nodes.machine = { config, ... }: {
+    environment.etc."guix/scripts".source = ./scripts;
+    services.guix = {
+      enable = true;
+      gc.enable = true;
+    };
+  };
+
+  testScript = ''
+    import pathlib
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("guix-daemon.service")
+    machine.succeed("systemctl start guix-gc.service")
+
+    # Can't do much here since the environment has restricted network access.
+    with subtest("Guix basic package management"):
+      machine.succeed("guix build --dry-run --verbosity=0 hello")
+      machine.succeed("guix show hello")
+
+    # This is to see if the Guix API is usable and mostly working.
+    with subtest("Guix API scripting"):
+      scripts_dir = pathlib.Path("/etc/guix/scripts")
+
+      text_msg = "Hello there, NixOS!"
+      text_store_file = machine.succeed(f"guix repl -- {scripts_dir}/create-file-to-store.scm '{text_msg}'")
+      assert machine.succeed(f"cat {text_store_file}") == text_msg
+
+      machine.succeed(f"guix repl -- {scripts_dir}/add-existing-files-to-store.scm {scripts_dir}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/guix/default.nix b/nixpkgs/nixos/tests/guix/default.nix
new file mode 100644
index 000000000000..a017668c05a7
--- /dev/null
+++ b/nixpkgs/nixos/tests/guix/default.nix
@@ -0,0 +1,8 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+
+{
+  basic = import ./basic.nix { inherit system pkgs; };
+  publish = import ./publish.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/guix/publish.nix b/nixpkgs/nixos/tests/guix/publish.nix
new file mode 100644
index 000000000000..eb56fc97478c
--- /dev/null
+++ b/nixpkgs/nixos/tests/guix/publish.nix
@@ -0,0 +1,96 @@
+# Testing out the substitute server with two machines in a local network. As a
+# bonus, we'll also test a feature of the substitute server being able to
+# advertise its service to the local network with Avahi.
+
+import ../make-test-python.nix ({ pkgs, lib, ... }: let
+  publishPort = 8181;
+  inherit (builtins) toString;
+in {
+  name = "guix-publish";
+
+  meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
+
+  nodes = let
+    commonConfig = { config, ... }: {
+      # We'll be using '--advertise' flag with the
+      # substitute server which requires Avahi.
+      services.avahi = {
+        enable = true;
+        nssmdns4 = true;
+        publish = {
+          enable = true;
+          userServices = true;
+        };
+      };
+    };
+  in {
+    server = { config, lib, pkgs, ... }: {
+      imports = [ commonConfig ];
+
+      services.guix = {
+        enable = true;
+        publish = {
+          enable = true;
+          port = publishPort;
+
+          generateKeyPair = true;
+          extraArgs = [ "--advertise" ];
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ publishPort ];
+    };
+
+    client = { config, lib, pkgs, ... }: {
+      imports = [ commonConfig ];
+
+      services.guix = {
+        enable = true;
+
+        extraArgs = [
+          # Force to only get all substitutes from the local server. We don't
+          # have anything in the Guix store directory and we cannot get
+          # anything from the official substitute servers anyways.
+          "--substitute-urls='http://server.local:${toString publishPort}'"
+
+          # Enable autodiscovery of the substitute servers in the local
+          # network. This machine shouldn't need to import the signing key from
+          # the substitute server since it is automatically done anyways.
+          "--discover=yes"
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    import pathlib
+
+    start_all()
+
+    scripts_dir = pathlib.Path("/etc/guix/scripts")
+
+    for machine in machines:
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("guix-daemon.service")
+      machine.wait_for_unit("avahi-daemon.service")
+
+    server.wait_for_unit("guix-publish.service")
+    server.wait_for_open_port(${toString publishPort})
+    server.succeed("curl http://localhost:${toString publishPort}/")
+
+    # Now it's the client turn to make use of it.
+    substitute_server = "http://server.local:${toString publishPort}"
+    client.systemctl("start network-online.target")
+    client.wait_for_unit("network-online.target")
+    response = client.succeed(f"curl {substitute_server}")
+    assert "Guix Substitute Server" in response
+
+    # Authorizing the server to be used as a substitute server.
+    client.succeed(f"curl -O {substitute_server}/signing-key.pub")
+    client.succeed("guix archive --authorize < ./signing-key.pub")
+
+    # Since we're using the substitute server with the `--advertise` flag, we
+    # might as well check it.
+    client.succeed("avahi-browse --resolve --terminate _guix_publish._tcp | grep '_guix_publish._tcp'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/guix/scripts/add-existing-files-to-store.scm b/nixpkgs/nixos/tests/guix/scripts/add-existing-files-to-store.scm
new file mode 100644
index 000000000000..fa47320b6a51
--- /dev/null
+++ b/nixpkgs/nixos/tests/guix/scripts/add-existing-files-to-store.scm
@@ -0,0 +1,52 @@
+;; A simple script that adds each file given from the command-line into the
+;; store and checks them if it's the same.
+(use-modules (guix)
+             (srfi srfi-1)
+             (ice-9 ftw)
+             (rnrs io ports))
+
+;; This is based from tests/derivations.scm from Guix source code.
+(define* (directory-contents dir #:optional (slurp get-bytevector-all))
+         "Return an alist representing the contents of DIR"
+         (define prefix-len (string-length dir))
+         (sort (file-system-fold (const #t)
+                                 (lambda (path stat result)
+                                   (alist-cons (string-drop path prefix-len)
+                                               (call-with-input-file path slurp)
+                                               result))
+                                 (lambda (path stat result) result)
+                                 (lambda (path stat result) result)
+                                 (lambda (path stat result) result)
+                                 (lambda (path stat errno result) result)
+                                 '()
+                                 dir)
+               (lambda (e1 e2)
+                 (string<? (car e1) (car e2)))))
+
+(define* (check-if-same store drv path)
+         "Check if the given path and its store item are the same"
+         (let* ((filetype (stat:type (stat drv))))
+           (case filetype
+             ((regular)
+              (and (valid-path? store drv)
+                   (equal? (call-with-input-file path get-bytevector-all)
+                           (call-with-input-file drv get-bytevector-all))))
+             ((directory)
+              (and (valid-path? store drv)
+                   (equal? (directory-contents path)
+                           (directory-contents drv))))
+             (else #f))))
+
+(define* (add-and-check-item-to-store store path)
+         "Add PATH to STORE and check if the contents are the same"
+         (let* ((store-item (add-to-store store
+                                          (basename path)
+                                          #t "sha256" path))
+                (is-same (check-if-same store store-item path)))
+           (if (not is-same)
+             (exit 1))))
+
+(with-store store
+            (map (lambda (path)
+                   (add-and-check-item-to-store store (readlink* path)))
+                 (cdr (command-line))))
diff --git a/nixpkgs/nixos/tests/guix/scripts/create-file-to-store.scm b/nixpkgs/nixos/tests/guix/scripts/create-file-to-store.scm
new file mode 100644
index 000000000000..467e4c4fd53f
--- /dev/null
+++ b/nixpkgs/nixos/tests/guix/scripts/create-file-to-store.scm
@@ -0,0 +1,8 @@
+;; A script that creates a store item with the given text and prints the
+;; resulting store item path.
+(use-modules (guix))
+
+(with-store store
+            (display (add-text-to-store store "guix-basic-test-text"
+                                        (string-join
+                                          (cdr (command-line))))))
diff --git a/nixpkgs/nixos/tests/gvisor.nix b/nixpkgs/nixos/tests/gvisor.nix
new file mode 100644
index 000000000000..7f130b709fc9
--- /dev/null
+++ b/nixpkgs/nixos/tests/gvisor.nix
@@ -0,0 +1,43 @@
+# This test runs a container through gvisor and checks if simple container starts
+
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "gvisor";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ andrew-d ];
+  };
+
+  nodes = {
+    gvisor =
+      { pkgs, ... }:
+      {
+        virtualisation.docker = {
+          enable = true;
+          extraOptions = "--add-runtime runsc=${pkgs.gvisor}/bin/runsc";
+        };
+
+        networking = {
+          dhcpcd.enable = false;
+          defaultGateway = "192.168.1.1";
+          interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+            { address = "192.168.1.2"; prefixLength = 24; }
+          ];
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+
+    gvisor.wait_for_unit("network.target")
+    gvisor.wait_for_unit("sockets.target")
+
+    # Test the Docker runtime
+    gvisor.succeed("tar cv --files-from /dev/null | docker import - scratchimg")
+    gvisor.succeed(
+        "docker run -d --name=sleeping --runtime=runsc -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+    )
+    gvisor.succeed("docker ps | grep sleeping")
+    gvisor.succeed("docker stop sleeping")
+  '';
+})
+
diff --git a/nixpkgs/nixos/tests/hadoop/default.nix b/nixpkgs/nixos/tests/hadoop/default.nix
new file mode 100644
index 000000000000..479690adc064
--- /dev/null
+++ b/nixpkgs/nixos/tests/hadoop/default.nix
@@ -0,0 +1,8 @@
+{ handleTestOn, package, ... }:
+
+{
+  all = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hadoop.nix { inherit package; };
+  hdfs = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hdfs.nix { inherit package; };
+  yarn = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./yarn.nix { inherit package; };
+  hbase = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./hbase.nix { inherit package; };
+}
diff --git a/nixpkgs/nixos/tests/hadoop/hadoop.nix b/nixpkgs/nixos/tests/hadoop/hadoop.nix
new file mode 100644
index 000000000000..6162ccfd33d4
--- /dev/null
+++ b/nixpkgs/nixos/tests/hadoop/hadoop.nix
@@ -0,0 +1,255 @@
+# This test is very comprehensive. It tests whether all hadoop services work well with each other.
+# Run this when updating the Hadoop package or making significant changes to the hadoop module.
+# For a more basic test, see hdfs.nix and yarn.nix
+import ../make-test-python.nix ({ package, ... }: {
+  name = "hadoop-combined";
+
+  nodes =
+    let
+      coreSite = {
+        "fs.defaultFS" = "hdfs://ns1";
+      };
+      hdfsSite = {
+        # HA Quorum Journal Manager configuration
+        "dfs.nameservices" = "ns1";
+        "dfs.ha.namenodes.ns1" = "nn1,nn2";
+        "dfs.namenode.shared.edits.dir.ns1" = "qjournal://jn1:8485;jn2:8485;jn3:8485/ns1";
+        "dfs.namenode.rpc-address.ns1.nn1" = "nn1:8020";
+        "dfs.namenode.rpc-address.ns1.nn2" = "nn2:8020";
+        "dfs.namenode.servicerpc-address.ns1.nn1" = "nn1:8022";
+        "dfs.namenode.servicerpc-address.ns1.nn2" = "nn2:8022";
+        "dfs.namenode.http-address.ns1.nn1" = "nn1:9870";
+        "dfs.namenode.http-address.ns1.nn2" = "nn2:9870";
+
+        # Automatic failover configuration
+        "dfs.client.failover.proxy.provider.ns1" = "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider";
+        "dfs.ha.automatic-failover.enabled.ns1" = "true";
+        "dfs.ha.fencing.methods" = "shell(true)";
+        "ha.zookeeper.quorum" = "zk1:2181";
+      };
+      yarnSite = {
+        "yarn.resourcemanager.zk-address" = "zk1:2181";
+        "yarn.resourcemanager.ha.enabled" = "true";
+        "yarn.resourcemanager.ha.rm-ids" = "rm1,rm2";
+        "yarn.resourcemanager.hostname.rm1" = "rm1";
+        "yarn.resourcemanager.hostname.rm2" = "rm2";
+        "yarn.resourcemanager.ha.automatic-failover.enabled" = "true";
+        "yarn.resourcemanager.cluster-id" = "cluster1";
+        # yarn.resourcemanager.webapp.address needs to be defined even though yarn.resourcemanager.hostname is set. This shouldn't be necessary, but there's a bug in
+        # hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/amfilter/AmFilterInitializer.java:70
+        # that causes AM containers to fail otherwise.
+        "yarn.resourcemanager.webapp.address.rm1" = "rm1:8088";
+        "yarn.resourcemanager.webapp.address.rm2" = "rm2:8088";
+      };
+    in
+    {
+      zk1 = { ... }: {
+        services.zookeeper.enable = true;
+        networking.firewall.allowedTCPPorts = [ 2181 ];
+      };
+
+      # HDFS cluster
+      nn1 = { ... }: {
+        services.hadoop = {
+          inherit package coreSite hdfsSite;
+          hdfs.namenode = {
+            enable = true;
+            openFirewall = true;
+          };
+          hdfs.zkfc.enable = true;
+        };
+      };
+      nn2 = { ... }: {
+        services.hadoop = {
+          inherit package coreSite hdfsSite;
+          hdfs.namenode = {
+            enable = true;
+            openFirewall = true;
+          };
+          hdfs.zkfc.enable = true;
+        };
+      };
+
+      jn1 = { ... }: {
+        services.hadoop = {
+          inherit package coreSite hdfsSite;
+          hdfs.journalnode = {
+            enable = true;
+            openFirewall = true;
+          };
+        };
+      };
+      jn2 = { ... }: {
+        services.hadoop = {
+          inherit package coreSite hdfsSite;
+          hdfs.journalnode = {
+            enable = true;
+            openFirewall = true;
+          };
+        };
+      };
+      jn3 = { ... }: {
+        services.hadoop = {
+          inherit package coreSite hdfsSite;
+          hdfs.journalnode = {
+            enable = true;
+            openFirewall = true;
+          };
+        };
+      };
+
+      dn1 = { ... }: {
+        services.hadoop = {
+          inherit package coreSite hdfsSite;
+          hdfs.datanode = {
+            enable = true;
+            openFirewall = true;
+          };
+        };
+      };
+
+      # YARN cluster
+      rm1 = { options, ... }: {
+        services.hadoop = {
+          inherit package coreSite hdfsSite yarnSite;
+          yarn.resourcemanager = {
+            enable = true;
+            openFirewall = true;
+          };
+        };
+      };
+      rm2 = { options, ... }: {
+        services.hadoop = {
+          inherit package coreSite hdfsSite yarnSite;
+          yarn.resourcemanager = {
+            enable = true;
+            openFirewall = true;
+          };
+        };
+      };
+      nm1 = { options, ... }: {
+        virtualisation.memorySize = 2048;
+        services.hadoop = {
+          inherit package coreSite hdfsSite yarnSite;
+          yarn.nodemanager = {
+            enable = true;
+            openFirewall = true;
+          };
+        };
+      };
+      client = { options, ... }: {
+        services.hadoop = {
+          gatewayRole.enable = true;
+          inherit package coreSite hdfsSite yarnSite;
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+
+    #### HDFS tests ####
+
+    zk1.wait_for_unit("network.target")
+    jn1.wait_for_unit("network.target")
+    jn2.wait_for_unit("network.target")
+    jn3.wait_for_unit("network.target")
+    nn1.wait_for_unit("network.target")
+    nn2.wait_for_unit("network.target")
+    dn1.wait_for_unit("network.target")
+
+    zk1.wait_for_unit("zookeeper")
+    jn1.wait_for_unit("hdfs-journalnode")
+    jn2.wait_for_unit("hdfs-journalnode")
+    jn3.wait_for_unit("hdfs-journalnode")
+
+    zk1.wait_for_open_port(2181)
+    jn1.wait_for_open_port(8480)
+    jn1.wait_for_open_port(8485)
+    jn2.wait_for_open_port(8480)
+    jn2.wait_for_open_port(8485)
+
+    # Namenodes must be stopped before initializing the cluster
+    nn1.succeed("systemctl stop hdfs-namenode")
+    nn2.succeed("systemctl stop hdfs-namenode")
+    nn1.succeed("systemctl stop hdfs-zkfc")
+    nn2.succeed("systemctl stop hdfs-zkfc")
+
+    # Initialize zookeeper for failover controller
+    nn1.succeed("sudo -u hdfs systemd-cat hdfs zkfc -formatZK")
+
+    # Format NN1 and start it
+    nn1.succeed("sudo -u hdfs systemd-cat hadoop namenode -format")
+    nn1.succeed("systemctl start hdfs-namenode")
+    nn1.wait_for_open_port(9870)
+    nn1.wait_for_open_port(8022)
+    nn1.wait_for_open_port(8020)
+
+    # Bootstrap NN2 from NN1 and start it
+    nn2.succeed("sudo -u hdfs systemd-cat hdfs namenode -bootstrapStandby")
+    nn2.succeed("systemctl start hdfs-namenode")
+    nn2.wait_for_open_port(9870)
+    nn2.wait_for_open_port(8022)
+    nn2.wait_for_open_port(8020)
+    nn1.succeed("systemd-cat netstat -tulpne")
+
+    # Start failover controllers
+    nn1.succeed("systemctl start hdfs-zkfc")
+    nn2.succeed("systemctl start hdfs-zkfc")
+
+    # DN should have started by now, but confirm anyway
+    dn1.wait_for_unit("hdfs-datanode")
+    # Print states of namenodes
+    client.succeed("sudo -u hdfs systemd-cat hdfs haadmin -getAllServiceState")
+    # Wait for cluster to exit safemode
+    client.succeed("sudo -u hdfs hdfs dfsadmin -safemode wait")
+    client.succeed("sudo -u hdfs systemd-cat hdfs haadmin -getAllServiceState")
+    # test R/W
+    client.succeed("echo testfilecontents | sudo -u hdfs hdfs dfs -put - /testfile")
+    assert "testfilecontents" in client.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
+
+    # Test NN failover
+    nn1.succeed("systemctl stop hdfs-namenode")
+    assert "active" in client.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState")
+    client.succeed("sudo -u hdfs systemd-cat hdfs haadmin -getAllServiceState")
+    assert "testfilecontents" in client.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
+
+    nn1.succeed("systemctl start hdfs-namenode")
+    nn1.wait_for_open_port(9870)
+    nn1.wait_for_open_port(8022)
+    nn1.wait_for_open_port(8020)
+    assert "standby" in client.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState")
+    client.succeed("sudo -u hdfs systemd-cat hdfs haadmin -getAllServiceState")
+
+    #### YARN tests ####
+
+    rm1.wait_for_unit("network.target")
+    rm2.wait_for_unit("network.target")
+    nm1.wait_for_unit("network.target")
+
+    rm1.wait_for_unit("yarn-resourcemanager")
+    rm1.wait_for_open_port(8088)
+    rm2.wait_for_unit("yarn-resourcemanager")
+    rm2.wait_for_open_port(8088)
+
+    nm1.wait_for_unit("yarn-nodemanager")
+    nm1.wait_for_open_port(8042)
+    nm1.wait_for_open_port(8040)
+    client.wait_until_succeeds("yarn node -list | grep Nodes:1")
+    client.succeed("sudo -u yarn systemd-cat yarn rmadmin -getAllServiceState")
+    client.succeed("sudo -u yarn systemd-cat yarn node -list")
+
+    # Test RM failover
+    rm1.succeed("systemctl stop yarn-resourcemanager")
+    assert "standby" not in client.succeed("sudo -u yarn yarn rmadmin -getAllServiceState")
+    client.succeed("sudo -u yarn systemd-cat yarn rmadmin -getAllServiceState")
+    rm1.succeed("systemctl start yarn-resourcemanager")
+    rm1.wait_for_unit("yarn-resourcemanager")
+    rm1.wait_for_open_port(8088)
+    assert "standby" in client.succeed("sudo -u yarn yarn rmadmin -getAllServiceState")
+    client.succeed("sudo -u yarn systemd-cat yarn rmadmin -getAllServiceState")
+
+    assert "Estimated value of Pi is" in client.succeed("HADOOP_USER_NAME=hdfs yarn jar $(readlink $(which yarn) | sed -r 's~bin/yarn~share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar~g') pi 2 10")
+    assert "SUCCEEDED" in client.succeed("yarn application -list -appStates FINISHED")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hadoop/hbase.nix b/nixpkgs/nixos/tests/hadoop/hbase.nix
new file mode 100644
index 000000000000..0416345682a8
--- /dev/null
+++ b/nixpkgs/nixos/tests/hadoop/hbase.nix
@@ -0,0 +1,109 @@
+# Test a minimal hbase cluster
+{ pkgs, ... }:
+import ../make-test-python.nix ({ hadoop ? pkgs.hadoop, hbase ? pkgs.hbase, ... }:
+with pkgs.lib;
+{
+  name = "hadoop-hbase";
+
+  nodes = let
+    coreSite = {
+      "fs.defaultFS" = "hdfs://namenode:8020";
+    };
+    defOpts = {
+      enable = true;
+      openFirewall = true;
+    };
+    zookeeperQuorum = "zookeeper";
+  in {
+    zookeeper = { ... }: {
+      services.zookeeper.enable = true;
+      networking.firewall.allowedTCPPorts = [ 2181 ];
+    };
+    namenode = { ... }: {
+      services.hadoop = {
+        hdfs = {
+          namenode = defOpts // { formatOnInit = true; };
+        };
+        inherit coreSite;
+      };
+    };
+    datanode = { ... }: {
+      virtualisation.diskSize = 8192;
+      services.hadoop = {
+        hdfs.datanode = defOpts;
+        inherit coreSite;
+      };
+    };
+
+    master = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          master = defOpts // { initHDFS = true; };
+        };
+      };
+    };
+    regionserver = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          regionServer = defOpts;
+        };
+      };
+    };
+    thrift = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          thrift = defOpts;
+        };
+      };
+    };
+    rest = { ... }:{
+      services.hadoop = {
+        inherit coreSite;
+        hbase = {
+          inherit zookeeperQuorum;
+          rest = defOpts;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    # wait for HDFS cluster
+    namenode.wait_for_unit("hdfs-namenode")
+    namenode.wait_for_unit("network.target")
+    namenode.wait_for_open_port(8020)
+    namenode.wait_for_open_port(9870)
+    datanode.wait_for_unit("hdfs-datanode")
+    datanode.wait_for_unit("network.target")
+    datanode.wait_for_open_port(9864)
+    datanode.wait_for_open_port(9866)
+    datanode.wait_for_open_port(9867)
+
+    # wait for ZK
+    zookeeper.wait_for_unit("zookeeper")
+    zookeeper.wait_for_open_port(2181)
+
+    # wait for HBase to start up
+    master.wait_for_unit("hbase-master")
+    regionserver.wait_for_unit("hbase-regionserver")
+
+    assert "1 active master, 0 backup masters, 1 servers" in master.succeed("echo status | HADOOP_USER_NAME=hbase hbase shell -n")
+    regionserver.wait_until_succeeds("echo \"create 't1','f1'\" | HADOOP_USER_NAME=hbase hbase shell -n")
+    assert "NAME => 'f1'" in regionserver.succeed("echo \"describe 't1'\" | HADOOP_USER_NAME=hbase hbase shell -n")
+
+    rest.wait_for_open_port(8080)
+    assert "${hbase.version}" in regionserver.succeed("curl http://rest:8080/version/cluster")
+
+    thrift.wait_for_open_port(9090)
+  '';
+
+  meta.maintainers = with maintainers; [ illustris ];
+})
diff --git a/nixpkgs/nixos/tests/hadoop/hdfs.nix b/nixpkgs/nixos/tests/hadoop/hdfs.nix
new file mode 100644
index 000000000000..65686b371559
--- /dev/null
+++ b/nixpkgs/nixos/tests/hadoop/hdfs.nix
@@ -0,0 +1,83 @@
+# Test a minimal HDFS cluster with no HA
+import ../make-test-python.nix ({ package, lib, ... }:
+{
+  name = "hadoop-hdfs";
+
+  nodes = let
+    coreSite = {
+      "fs.defaultFS" = "hdfs://namenode:8020";
+      "hadoop.proxyuser.httpfs.groups" = "*";
+      "hadoop.proxyuser.httpfs.hosts" = "*";
+    };
+    in {
+    namenode = { pkgs, ... }: {
+      services.hadoop = {
+        inherit package;
+        hdfs = {
+          namenode = {
+            enable = true;
+            openFirewall = true;
+            formatOnInit = true;
+          };
+          httpfs = {
+            # The NixOS hadoop module only support webHDFS on 3.3 and newer
+            enable = lib.mkIf (lib.versionAtLeast package.version "3.3") true;
+            openFirewall = true;
+          };
+        };
+        inherit coreSite;
+      };
+    };
+    datanode = { pkgs, ... }: {
+      services.hadoop = {
+        inherit package;
+        hdfs.datanode = {
+          enable = true;
+          openFirewall = true;
+          dataDirs = [{
+            type = "DISK";
+            path = "/tmp/dn1";
+          }];
+        };
+        inherit coreSite;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    namenode.wait_for_unit("hdfs-namenode")
+    namenode.wait_for_unit("network.target")
+    namenode.wait_for_open_port(8020)
+    namenode.succeed("systemd-cat ss -tulpne")
+    namenode.succeed("systemd-cat cat /etc/hadoop*/hdfs-site.xml")
+    namenode.wait_for_open_port(9870)
+
+    datanode.wait_for_unit("hdfs-datanode")
+    datanode.wait_for_unit("network.target")
+  '' + (if lib.versionAtLeast package.version "3" then ''
+    datanode.wait_for_open_port(9864)
+    datanode.wait_for_open_port(9866)
+    datanode.wait_for_open_port(9867)
+
+    datanode.succeed("curl -f http://datanode:9864")
+  '' else ''
+    datanode.wait_for_open_port(50075)
+    datanode.wait_for_open_port(50010)
+    datanode.wait_for_open_port(50020)
+
+    datanode.succeed("curl -f http://datanode:50075")
+  '' ) + ''
+    namenode.succeed("curl -f http://namenode:9870")
+
+    datanode.succeed("sudo -u hdfs hdfs dfsadmin -safemode wait")
+    datanode.succeed("echo testfilecontents | sudo -u hdfs hdfs dfs -put - /testfile")
+    assert "testfilecontents" in datanode.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
+
+  '' + lib.optionalString (lib.versionAtLeast package.version "3.3" ) ''
+    namenode.wait_for_unit("hdfs-httpfs")
+    namenode.wait_for_open_port(14000)
+    assert "testfilecontents" in datanode.succeed("curl -f \"http://namenode:14000/webhdfs/v1/testfile?user.name=hdfs&op=OPEN\" 2>&1")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hadoop/yarn.nix b/nixpkgs/nixos/tests/hadoop/yarn.nix
new file mode 100644
index 000000000000..08c8ff857d8c
--- /dev/null
+++ b/nixpkgs/nixos/tests/hadoop/yarn.nix
@@ -0,0 +1,45 @@
+# This only tests if YARN is able to start its services
+import ../make-test-python.nix ({ package, ... }: {
+  name = "hadoop-yarn";
+
+  nodes = {
+    resourcemanager = { ... }: {
+      services.hadoop = {
+        inherit package;
+        yarn.resourcemanager = {
+          enable = true;
+          openFirewall = true;
+        };
+      };
+    };
+    nodemanager = { options, lib, ... }: {
+      services.hadoop = {
+        inherit package;
+        yarn.nodemanager = {
+          enable = true;
+          openFirewall = true;
+        };
+        yarnSite = {
+          "yarn.resourcemanager.hostname" = "resourcemanager";
+          "yarn.nodemanager.log-dirs" = "/tmp/userlogs";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    resourcemanager.wait_for_unit("yarn-resourcemanager")
+    resourcemanager.wait_for_unit("network.target")
+    resourcemanager.wait_for_open_port(8031)
+    resourcemanager.wait_for_open_port(8088)
+
+    nodemanager.wait_for_unit("yarn-nodemanager")
+    nodemanager.wait_for_unit("network.target")
+    nodemanager.wait_for_open_port(8042)
+
+    resourcemanager.succeed("curl -f http://localhost:8088")
+    nodemanager.succeed("curl -f http://localhost:8042")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/haka.nix b/nixpkgs/nixos/tests/haka.nix
new file mode 100644
index 000000000000..dd65a6bcf115
--- /dev/null
+++ b/nixpkgs/nixos/tests/haka.nix
@@ -0,0 +1,24 @@
+# This test runs haka and probes it with hakactl
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "haka";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tvestelind ];
+  };
+
+  nodes = {
+    haka =
+      { ... }:
+        {
+          services.haka.enable = true;
+        };
+    };
+
+  testScript = ''
+    start_all()
+
+    haka.wait_for_unit("haka.service")
+    haka.succeed("hakactl status")
+    haka.succeed("hakactl stop")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/haproxy.nix b/nixpkgs/nixos/tests/haproxy.nix
new file mode 100644
index 000000000000..173093873757
--- /dev/null
+++ b/nixpkgs/nixos/tests/haproxy.nix
@@ -0,0 +1,124 @@
+import ./make-test-python.nix ({ lib, pkgs, ...}: {
+  name = "haproxy";
+  nodes = {
+    server = { ... }: {
+     services.haproxy = {
+        enable = true;
+        config = ''
+          global
+            limited-quic
+
+          defaults
+            mode http
+            timeout connect 10s
+            timeout client 10s
+            timeout server 10s
+
+            log /dev/log local0 debug err
+            option logasap
+            option httplog
+            option httpslog
+
+          backend http_server
+            server httpd [::1]:8000 alpn http/1.1
+
+          frontend http
+            bind :80
+            bind :443 ssl strict-sni crt /etc/ssl/fullchain.pem alpn h2,http/1.1
+            bind quic4@:443 ssl strict-sni crt /etc/ssl/fullchain.pem alpn h3 allow-0rtt
+
+            http-after-response add-header alt-svc 'h3=":443"; ma=60' if { ssl_fc }
+
+            http-request use-service prometheus-exporter if { path /metrics }
+            use_backend http_server
+
+          frontend http-cert-auth
+            bind :8443 ssl strict-sni crt /etc/ssl/fullchain.pem verify required ca-file /etc/ssl/cacert.crt
+            bind quic4@:8443 ssl strict-sni crt /etc/ssl/fullchain.pem verify required ca-file /etc/ssl/cacert.crt alpn h3
+
+            use_backend http_server
+        '';
+      };
+      services.httpd = {
+        enable = true;
+        virtualHosts.localhost = {
+          documentRoot = pkgs.writeTextDir "index.txt" "We are all good!";
+          adminAddr = "notme@yourhost.local";
+          listen = [{
+            ip = "::1";
+            port = 8000;
+          }];
+        };
+      };
+      networking.firewall.allowedTCPPorts = [ 80 443 8443 ];
+      networking.firewall.allowedUDPPorts = [ 443 8443 ];
+     };
+    client = { ... }: {
+      environment.systemPackages = [ pkgs.curlHTTP3 ];
+    };
+  };
+  testScript = ''
+    # Helpers
+    def cmd(command):
+      print(f"+{command}")
+      r = os.system(command)
+      if r != 0:
+        raise Exception(f"Command {command} failed with exit code {r}")
+
+    def openssl(command):
+      cmd(f"${pkgs.openssl}/bin/openssl {command}")
+
+    # Generate CA.
+    openssl("req -new -newkey rsa:4096 -nodes -x509 -days 7 -subj '/C=ZZ/ST=Cloud/L=Unspecified/O=NixOS/OU=Tests/CN=CA Certificate' -keyout cacert.key -out cacert.crt")
+
+    # Generate and sign Server.
+    openssl("req -newkey rsa:4096 -nodes -subj '/CN=server/OU=Tests/O=NixOS' -keyout server.key -out server.csr")
+    openssl("x509 -req -in server.csr -out server.crt -CA cacert.crt -CAkey cacert.key -days 7")
+    cmd("cat server.crt server.key > fullchain.pem")
+
+    # Generate and sign Client.
+    openssl("req -newkey rsa:4096 -nodes -subj '/CN=client/OU=Tests/O=NixOS' -keyout client.key -out client.csr")
+    openssl("x509 -req -in client.csr -out client.crt -CA cacert.crt -CAkey cacert.key -days 7")
+    cmd("cat client.crt client.key > client.pem")
+
+    # Start the actual test.
+    start_all()
+    server.copy_from_host("fullchain.pem", "/etc/ssl/fullchain.pem")
+    server.copy_from_host("cacert.crt", "/etc/ssl/cacert.crt")
+    server.succeed("chmod 0644 /etc/ssl/fullchain.pem /etc/ssl/cacert.crt")
+
+    client.copy_from_host("cacert.crt", "/etc/ssl/cacert.crt")
+    client.copy_from_host("client.pem", "/root/client.pem")
+
+    server.wait_for_unit("multi-user.target")
+    server.wait_for_unit("haproxy.service")
+    server.wait_for_unit("httpd.service")
+
+    assert "We are all good!" in client.succeed("curl -f http://server/index.txt")
+    assert "haproxy_process_pool_allocated_bytes" in client.succeed("curl -f http://server/metrics")
+
+    with subtest("https"):
+      assert "We are all good!" in client.succeed("curl -f --cacert /etc/ssl/cacert.crt https://server/index.txt")
+
+    with subtest("https-cert-auth"):
+      # Client must succeed in authenticating with the right certificate.
+      assert "We are all good!" in client.succeed("curl -f --cacert /etc/ssl/cacert.crt --cert-type pem --cert /root/client.pem https://server:8443/index.txt")
+      # Client must fail without certificate.
+      client.fail("curl --cacert /etc/ssl/cacert.crt https://server:8443/index.txt")
+
+    with subtest("h3"):
+      assert "We are all good!" in client.succeed("curl -f --http3-only --cacert /etc/ssl/cacert.crt https://server/index.txt")
+
+    with subtest("h3-cert-auth"):
+      # Client must succeed in authenticating with the right certificate.
+      assert "We are all good!" in client.succeed("curl -f --http3-only --cacert /etc/ssl/cacert.crt --cert-type pem --cert /root/client.pem https://server:8443/index.txt")
+      # Client must fail without certificate.
+      client.fail("curl -f --http3-only --cacert /etc/ssl/cacert.crt https://server:8443/index.txt")
+
+    with subtest("reload"):
+        server.succeed("systemctl reload haproxy")
+        # wait some time to ensure the following request hits the reloaded haproxy
+        server.sleep(5)
+        assert "We are all good!" in client.succeed("curl -f http://server/index.txt")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hardened.nix b/nixpkgs/nixos/tests/hardened.nix
new file mode 100644
index 000000000000..e38834961e13
--- /dev/null
+++ b/nixpkgs/nixos/tests/hardened.nix
@@ -0,0 +1,105 @@
+import ./make-test-python.nix ({ pkgs, ... } : {
+  name = "hardened";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ joachifm ];
+  };
+
+  nodes.machine =
+    { lib, pkgs, config, ... }:
+    { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
+      users.users.sybil = { isNormalUser = true; group = "wheel"; };
+      imports = [ ../modules/profiles/hardened.nix ];
+      environment.memoryAllocator.provider = "graphene-hardened";
+      nix.settings.sandbox = false;
+      nixpkgs.overlays = [
+        (final: super: {
+          dhcpcd = super.dhcpcd.override { enablePrivSep = false; };
+        })
+      ];
+      virtualisation.emptyDiskImages = [ 4096 ];
+      boot.initrd.postDeviceCommands = ''
+        ${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
+      '';
+      virtualisation.fileSystems = {
+        "/efi" = {
+          device = "/dev/disk/by-label/EFISYS";
+          fsType = "vfat";
+          options = [ "noauto" ];
+        };
+      };
+      boot.extraModulePackages =
+        pkgs.lib.optional (pkgs.lib.versionOlder config.boot.kernelPackages.kernel.version "5.6")
+          config.boot.kernelPackages.wireguard;
+      boot.kernelModules = [ "wireguard" ];
+    };
+
+  testScript =
+    let
+      hardened-malloc-tests = pkgs.graphene-hardened-malloc.ld-preload-tests;
+    in
+    ''
+      machine.wait_for_unit("multi-user.target")
+
+
+      with subtest("AppArmor profiles are loaded"):
+          machine.succeed("systemctl status apparmor.service")
+
+
+      # AppArmor securityfs
+      with subtest("AppArmor securityfs is mounted"):
+          machine.succeed("mountpoint -q /sys/kernel/security")
+          machine.succeed("cat /sys/kernel/security/apparmor/profiles")
+
+
+      # Test loading out-of-tree modules
+      with subtest("Out-of-tree modules can be loaded"):
+          machine.succeed("grep -Fq wireguard /proc/modules")
+
+
+      # Test kernel module hardening
+      with subtest("No more kernel modules can be loaded"):
+          # note: this better a be module we normally wouldn't load ...
+          machine.wait_for_unit("disable-kernel-module-loading.service")
+          machine.fail("modprobe dccp")
+
+
+      # Test userns
+      with subtest("User namespaces are restricted"):
+          machine.succeed("unshare --user true")
+          machine.fail("su -l alice -c 'unshare --user true'")
+
+
+      # Test dmesg restriction
+      with subtest("Regular users cannot access dmesg"):
+          machine.fail("su -l alice -c dmesg")
+
+
+      # Test access to kcore
+      with subtest("Kcore is inaccessible as root"):
+          machine.fail("cat /proc/kcore")
+
+
+      # Test deferred mount
+      with subtest("Deferred mounts work"):
+          machine.fail("mountpoint -q /efi")  # was deferred
+          machine.execute("mkdir -p /efi")
+          machine.succeed("mount /dev/disk/by-label/EFISYS /efi")
+          machine.succeed("mountpoint -q /efi")  # now mounted
+
+
+      # Test Nix dæmon usage
+      with subtest("nix-daemon cannot be used by all users"):
+          machine.fail("su -l nobody -s /bin/sh -c 'nix --extra-experimental-features nix-command ping-store'")
+          machine.succeed("su -l alice -c 'nix --extra-experimental-features nix-command ping-store'")
+
+
+      # Test kernel image protection
+      with subtest("The kernel image is protected"):
+          machine.fail("systemctl hibernate")
+          machine.fail("systemctl kexec")
+
+
+      with subtest("The hardened memory allocator works"):
+          machine.succeed("${hardened-malloc-tests}/bin/run-tests")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/harmonia.nix b/nixpkgs/nixos/tests/harmonia.nix
new file mode 100644
index 000000000000..a9beac82f8e1
--- /dev/null
+++ b/nixpkgs/nixos/tests/harmonia.nix
@@ -0,0 +1,40 @@
+{ pkgs, lib, ... }:
+
+{
+  name = "harmonia";
+
+  nodes = {
+    harmonia = {
+      services.harmonia = {
+        enable = true;
+        signKeyPath = pkgs.writeText "cache-key" "cache.example.com-1:9FhO0w+7HjZrhvmzT1VlAZw4OSAlFGTgC24Seg3tmPl4gZBdwZClzTTHr9cVzJpwsRSYLTu7hEAQe3ljy92CWg==";
+        settings.priority = 35;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 5000 ];
+      system.extraDependencies = [ pkgs.emptyFile ];
+
+      # check that extra-allowed-users is effective for harmonia
+      nix.settings.allowed-users = [];
+    };
+
+    client01 = {
+      nix.settings = {
+        substituters = lib.mkForce [ "http://harmonia:5000" ];
+        trusted-public-keys = lib.mkForce [ "cache.example.com-1:eIGQXcGQpc00x6/XFcyacLEUmC07u4RAEHt5Y8vdglo=" ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    harmonia.wait_for_unit("harmonia.service")
+
+    client01.wait_until_succeeds("curl -f http://harmonia:5000/nix-cache-info | grep '${toString nodes.harmonia.services.harmonia.settings.priority}' >&2")
+    client01.succeed("curl -f http://harmonia:5000/version | grep '${nodes.harmonia.services.harmonia.package.version}' >&2")
+
+    client01.succeed("cat /etc/nix/nix.conf >&2")
+    client01.succeed("nix-store --realise ${pkgs.emptyFile} --store /root/other-store")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/haste-server.nix b/nixpkgs/nixos/tests/haste-server.nix
new file mode 100644
index 000000000000..9097c992c548
--- /dev/null
+++ b/nixpkgs/nixos/tests/haste-server.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  {
+    name = "haste-server";
+    meta.maintainers = with lib.maintainers; [ mkg20001 ];
+
+    nodes.machine = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [
+        curl
+        jq
+      ];
+
+      services.haste-server = {
+        enable = true;
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("haste-server")
+      machine.wait_until_succeeds("curl -s localhost:7777")
+      machine.succeed('curl -s -X POST http://localhost:7777/documents -d "Hello World!" > bla')
+      machine.succeed('curl http://localhost:7777/raw/$(cat bla | jq -r .key) | grep "Hello World"')
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/hbase.nix b/nixpkgs/nixos/tests/hbase.nix
new file mode 100644
index 000000000000..7d8e32f81603
--- /dev/null
+++ b/nixpkgs/nixos/tests/hbase.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, lib, package ? pkgs.hbase, ... }:
+{
+  name = "hbase-standalone";
+
+  meta = with lib.maintainers; {
+    maintainers = [ illustris ];
+  };
+
+  nodes = {
+    hbase = { pkgs, ... }: {
+      services.hbase-standalone = {
+        enable = true;
+        inherit package;
+        # Needed for standalone mode in hbase 2+
+        # This setting and standalone mode are not suitable for production
+        settings."hbase.unsafe.stream.capability.enforce" = "false";
+      };
+      environment.systemPackages = with pkgs; [
+        package
+      ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+    hbase.wait_for_unit("hbase.service")
+    hbase.wait_until_succeeds("echo \"create 't1','f1'\" | sudo -u hbase hbase shell -n")
+    assert "NAME => 'f1'" in hbase.succeed("echo \"describe 't1'\" | sudo -u hbase hbase shell -n")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hddfancontrol.nix b/nixpkgs/nixos/tests/hddfancontrol.nix
new file mode 100644
index 000000000000..b5fa7ccb2c19
--- /dev/null
+++ b/nixpkgs/nixos/tests/hddfancontrol.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "hddfancontrol";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ benley ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    services.hddfancontrol.enable = true;
+    services.hddfancontrol.disks = ["/dev/vda"];
+    services.hddfancontrol.pwmPaths = ["/test/hwmon1/pwm1"];
+    services.hddfancontrol.extraArgs = ["--pwm-start-value=32"
+                                        "--pwm-stop-value=0"];
+
+    systemd.services.hddfancontrol_fixtures = {
+      description = "Install test fixtures for hddfancontrol";
+      serviceConfig = {
+        Type = "oneshot";
+      };
+      script = ''
+        mkdir -p /test/hwmon1
+        echo 255 > /test/hwmon1/pwm1
+        echo 2 > /test/hwmon1/pwm1_enable
+      '';
+      wantedBy = ["hddfancontrol.service"];
+      before = ["hddfancontrol.service"];
+    };
+
+    systemd.services.hddfancontrol.serviceConfig.ReadWritePaths = "/test";
+  };
+
+  # hddfancontrol.service will fail to start because qemu /dev/vda doesn't have
+  # any thermal interfaces, but it should ensure that fans appear to be running
+  # before it aborts.
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("journalctl -eu hddfancontrol.service|grep 'Setting fan speed'")
+    machine.shutdown()
+
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/headscale.nix b/nixpkgs/nixos/tests/headscale.nix
new file mode 100644
index 000000000000..80188b65dbfc
--- /dev/null
+++ b/nixpkgs/nixos/tests/headscale.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    tls-cert =
+      pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+        openssl req \
+          -x509 -newkey rsa:4096 -sha256 -days 365 \
+          -nodes -out cert.pem -keyout key.pem \
+          -subj '/CN=headscale' -addext "subjectAltName=DNS:headscale"
+
+        mkdir -p $out
+        cp key.pem cert.pem $out
+      '';
+  in {
+    name = "headscale";
+    meta.maintainers = with lib.maintainers; [ misterio77 ];
+
+    nodes = let
+      headscalePort = 8080;
+      stunPort = 3478;
+      peer = {
+        services.tailscale.enable = true;
+        security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+      };
+    in {
+      peer1 = peer;
+      peer2 = peer;
+
+      headscale = {
+        services = {
+          headscale = {
+            enable = true;
+            port = headscalePort;
+            settings = {
+              server_url = "https://headscale";
+              ip_prefixes = [ "100.64.0.0/10" ];
+              derp.server = {
+                enabled = true;
+                region_id = 999;
+                stun_listen_addr = "0.0.0.0:${toString stunPort}";
+              };
+            };
+          };
+          nginx = {
+            enable = true;
+            virtualHosts.headscale = {
+              addSSL = true;
+              sslCertificate = "${tls-cert}/cert.pem";
+              sslCertificateKey = "${tls-cert}/key.pem";
+              locations."/" = {
+                proxyPass = "http://127.0.0.1:${toString headscalePort}";
+                proxyWebsockets = true;
+              };
+            };
+          };
+        };
+        networking.firewall = {
+          allowedTCPPorts = [ 80 443 ];
+          allowedUDPPorts = [ stunPort ];
+        };
+        environment.systemPackages = [ pkgs.headscale ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+      headscale.wait_for_unit("headscale")
+      headscale.wait_for_open_port(443)
+
+      # Create headscale user and preauth-key
+      headscale.succeed("headscale users create test")
+      authkey = headscale.succeed("headscale preauthkeys -u test create --reusable")
+
+      # Connect peers
+      up_cmd = f"tailscale up --login-server 'https://headscale' --auth-key {authkey}"
+      peer1.execute(up_cmd)
+      peer2.execute(up_cmd)
+
+      # Check that they are reachable from the tailnet
+      peer1.wait_until_succeeds("tailscale ping peer2")
+      peer2.wait_until_succeeds("tailscale ping peer1")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/hedgedoc.nix b/nixpkgs/nixos/tests/hedgedoc.nix
new file mode 100644
index 000000000000..16e0dc14e947
--- /dev/null
+++ b/nixpkgs/nixos/tests/hedgedoc.nix
@@ -0,0 +1,96 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "hedgedoc";
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  nodes = {
+    hedgedocSqlite = { ... }: {
+      services.hedgedoc.enable = true;
+    };
+
+    hedgedocPostgresWithTCPSocket = { ... }: {
+      systemd.services.hedgedoc.after = [ "postgresql.service" ];
+      services = {
+        hedgedoc = {
+          enable = true;
+          settings.db = {
+            dialect = "postgres";
+            user = "hedgedoc";
+            password = "$DB_PASSWORD";
+            host = "localhost";
+            port = 5432;
+            database = "hedgedocdb";
+          };
+
+          /*
+           * Do not use pkgs.writeText for secrets as
+           * they will end up in the world-readable Nix store.
+           */
+          environmentFile = pkgs.writeText "hedgedoc-env" ''
+            DB_PASSWORD=snakeoilpassword
+          '';
+        };
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script.sql" ''
+            CREATE ROLE hedgedoc LOGIN PASSWORD 'snakeoilpassword';
+            CREATE DATABASE hedgedocdb OWNER hedgedoc;
+          '';
+        };
+      };
+    };
+
+    hedgedocPostgresWithUNIXSocket = { ... }: {
+      systemd.services.hedgedoc.after = [ "postgresql.service" ];
+      services = {
+        hedgedoc = {
+          enable = true;
+          settings.db = {
+            dialect = "postgres";
+            user = "hedgedoc";
+            password = "$DB_PASSWORD";
+            host = "/run/postgresql";
+            database = "hedgedocdb";
+          };
+
+          environmentFile = pkgs.writeText "hedgedoc-env" ''
+            DB_PASSWORD=snakeoilpassword
+          '';
+        };
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script.sql" ''
+            CREATE ROLE hedgedoc LOGIN PASSWORD 'snakeoilpassword';
+            CREATE DATABASE hedgedocdb OWNER hedgedoc;
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("HedgeDoc sqlite"):
+        hedgedocSqlite.wait_for_unit("hedgedoc.service")
+        hedgedocSqlite.wait_for_open_port(3000)
+        hedgedocSqlite.wait_until_succeeds("curl -sSf http://localhost:3000/new")
+
+    with subtest("HedgeDoc postgres with TCP socket"):
+        hedgedocPostgresWithTCPSocket.wait_for_unit("postgresql.service")
+        hedgedocPostgresWithTCPSocket.wait_for_unit("hedgedoc.service")
+        hedgedocPostgresWithTCPSocket.wait_for_open_port(5432)
+        hedgedocPostgresWithTCPSocket.wait_for_open_port(3000)
+        hedgedocPostgresWithTCPSocket.wait_until_succeeds("curl -sSf http://localhost:3000/new")
+
+    with subtest("HedgeDoc postgres with UNIX socket"):
+        hedgedocPostgresWithUNIXSocket.wait_for_unit("postgresql.service")
+        hedgedocPostgresWithUNIXSocket.wait_for_unit("hedgedoc.service")
+        hedgedocPostgresWithUNIXSocket.wait_for_open_port(5432)
+        hedgedocPostgresWithUNIXSocket.wait_for_open_port(3000)
+        hedgedocPostgresWithUNIXSocket.wait_until_succeeds("curl -sSf http://localhost:3000/new")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/herbstluftwm.nix b/nixpkgs/nixos/tests/herbstluftwm.nix
new file mode 100644
index 000000000000..b6965914360e
--- /dev/null
+++ b/nixpkgs/nixos/tests/herbstluftwm.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ lib, ...} : {
+  name = "herbstluftwm";
+
+  meta = {
+    maintainers = with lib.maintainers; [ thibautmarty ];
+  };
+
+  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";
+    services.xserver.windowManager.herbstluftwm.enable = true;
+    environment.systemPackages = [ pkgs.dzen2 ]; # needed for upstream provided panel
+  };
+
+  testScript = ''
+    with subtest("ensure x starts"):
+        machine.wait_for_x()
+        machine.wait_for_file("/home/alice/.Xauthority")
+        machine.succeed("xauth merge ~alice/.Xauthority")
+
+    with subtest("ensure client is available"):
+        machine.succeed("herbstclient --version")
+
+    with subtest("ensure keybindings are set"):
+        machine.wait_until_succeeds("herbstclient list_keybinds | grep xterm")
+
+    with subtest("ensure panel starts"):
+        machine.wait_for_window("dzen title")
+
+    with subtest("ensure we can open a new terminal"):
+        machine.send_key("alt-ret")
+        machine.wait_for_window(r"alice.*?machine")
+        machine.sleep(2)
+        machine.screenshot("terminal")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hibernate.nix b/nixpkgs/nixos/tests/hibernate.nix
new file mode 100644
index 000000000000..296aa9ba68b9
--- /dev/null
+++ b/nixpkgs/nixos/tests/hibernate.nix
@@ -0,0 +1,55 @@
+# Test whether hibernation from partition works.
+
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, systemdStage1 ? false
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+makeTest {
+  name = "hibernate";
+
+  nodes = {
+    machine = { config, lib, pkgs, ... }: {
+      imports = [
+        ./common/auto-format-root-device.nix
+      ];
+
+      systemd.services.backdoor.conflicts = [ "sleep.target" ];
+      powerManagement.resumeCommands = "systemctl --no-block restart backdoor.service";
+
+      virtualisation.emptyDiskImages = [ (2 * config.virtualisation.memorySize) ];
+      virtualisation.useNixStoreImage = true;
+
+      swapDevices = lib.mkOverride 0 [ { device = "/dev/vdc"; options = [ "x-systemd.makefs" ]; } ];
+      boot.resumeDevice = "/dev/vdc";
+      boot.initrd.systemd.enable = systemdStage1;
+    };
+  };
+
+  testScript = ''
+    # Drop in file that checks if we un-hibernated properly (and not booted fresh)
+    machine.wait_for_unit("default.target")
+    machine.succeed(
+        "mkdir /run/test",
+        "mount -t ramfs -o size=1m ramfs /run/test",
+        "echo not persisted to disk > /run/test/suspended",
+    )
+
+    # Hibernate machine
+    machine.execute("systemctl hibernate >&2 &", check_return=False)
+    machine.wait_for_shutdown()
+
+    # Restore machine from hibernation, validate our ramfs file is there.
+    machine.start()
+    machine.succeed("grep 'not persisted to disk' /run/test/suspended")
+
+    # Ensure we don't restore from hibernation when booting again
+    machine.crash()
+    machine.wait_for_unit("default.target")
+    machine.fail("grep 'not persisted to disk' /run/test/suspended")
+  '';
+
+}
diff --git a/nixpkgs/nixos/tests/hitch/default.nix b/nixpkgs/nixos/tests/hitch/default.nix
new file mode 100644
index 000000000000..4283b9f7dffb
--- /dev/null
+++ b/nixpkgs/nixos/tests/hitch/default.nix
@@ -0,0 +1,33 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+{
+  name = "hitch";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ jflanglois ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.hitch = {
+      enable = true;
+      backend = "[127.0.0.1]:80";
+      pem-files = [
+        ./example.pem
+      ];
+    };
+
+    services.httpd = {
+      enable = true;
+      virtualHosts.localhost.documentRoot = ./example;
+      adminAddr = "noone@testing.nowhere";
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("hitch.service")
+      machine.wait_for_open_port(443)
+      assert "We are all good!" in machine.succeed("curl -fk https://localhost:443/index.txt")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/hitch/example.pem b/nixpkgs/nixos/tests/hitch/example.pem
new file mode 100644
index 000000000000..fde6f3cbd19a
--- /dev/null
+++ b/nixpkgs/nixos/tests/hitch/example.pem
@@ -0,0 +1,53 @@
+-----BEGIN CERTIFICATE-----
+MIIEKTCCAxGgAwIBAgIJAIFAWQXSZ7lIMA0GCSqGSIb3DQEBCwUAMIGqMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMUmVkd29vZCBD
+aXR5MRkwFwYDVQQKDBBUZXN0aW5nIDEyMyBJbmMuMRQwEgYDVQQLDAtJVCBTZXJ2
+aWNlczEYMBYGA1UEAwwPdGVzdGluZy5ub3doZXJlMSQwIgYJKoZIhvcNAQkBFhVu
+b29uZUB0ZXN0aW5nLm5vd2hlcmUwHhcNMTgwNDIzMDcxMTI5WhcNMTkwNDIzMDcx
+MTI5WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNV
+BAcMDFJlZHdvb2QgQ2l0eTEZMBcGA1UECgwQVGVzdGluZyAxMjMgSW5jLjEUMBIG
+A1UECwwLSVQgU2VydmljZXMxGDAWBgNVBAMMD3Rlc3Rpbmcubm93aGVyZTEkMCIG
+CSqGSIb3DQEJARYVbm9vbmVAdGVzdGluZy5ub3doZXJlMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAxQq6AA9o/QErMbQwfgDF4mqXcvglRTwPr2zPE6Rv
+1g0ncRBSMM8iKbPapHM6qHNfg2e1fU2SFqzD6HkyZqHHLCgLzkdzswEcEjsMqiUP
+OR++5g4CWoQrdTi31itzYzCjnQ45BrAMrLEhBQgDTNwrEE+Tit0gpOGggtj/ktLk
+OD8BKa640lkmWEUGF18fd3rYTUC4hwM5qhAVXTe21vj9ZWsgprpQKdN61v0dCUap
+C5eAgvZ8Re+Cd0Id674hK4cJ4SekqfHKv/jLyIg3Vsdc9nkhmiC4O6KH5f1Zzq2i
+E4Kd5mnJDFxfSzIErKWmbhriLWsj3KEJ983AGLJ9hxQTAwIDAQABo1AwTjAdBgNV
+HQ4EFgQU76Mm6DP/BePJRQUNrJ9z038zjocwHwYDVR0jBBgwFoAU76Mm6DP/BePJ
+RQUNrJ9z038zjocwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAAZzt
+VdPaUqrvDAh5rMYqzYMJ3tj6daNYoX6CbTFoevK5J5D4FESM0D/FMKgpNiVz39kB
+8Cjaw5rPHMHY61rHz7JRDK1sWXsonwzCF21BK7Tx0G1CIfLpYHWYb/FfdWGROx+O
+hPgKuoMRWQB+txozkZp5BqWJmk5MOyFCDEXhMOmrfsJq0IYU6QaH3Lsf1oJRy4yU
+afFrT9o3DLOyYLG/j/HXijCu8DVjZVa4aboum79ecYzPjjGF1posrFUnvQiuAeYy
+t7cuHNUB8gW9lWR5J7tP8fzFWtIcyT2oRL8u3H+fXf0i4bW73wtOBOoeULBzBNE7
+6rphcSrQunSZQIc+hg==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFCroAD2j9ASsx
+tDB+AMXiapdy+CVFPA+vbM8TpG/WDSdxEFIwzyIps9qkczqoc1+DZ7V9TZIWrMPo
+eTJmoccsKAvOR3OzARwSOwyqJQ85H77mDgJahCt1OLfWK3NjMKOdDjkGsAyssSEF
+CANM3CsQT5OK3SCk4aCC2P+S0uQ4PwEprrjSWSZYRQYXXx93ethNQLiHAzmqEBVd
+N7bW+P1layCmulAp03rW/R0JRqkLl4CC9nxF74J3Qh3rviErhwnhJ6Sp8cq/+MvI
+iDdWx1z2eSGaILg7oofl/VnOraITgp3mackMXF9LMgSspaZuGuItayPcoQn3zcAY
+sn2HFBMDAgMBAAECggEAcaR8HijFHpab+PC5vxJnDuz3KEHiDQpU6ZJR5DxEnCm+
+A8GsBaaRR4gJpCspO5o/DiS0Ue55QUanPt8XqIXJv7fhBznCiw0qyYDxDviMzR94
+FGskBFySS+tIa+dnh1+4HY7kaO0Egl0udB5o+N1KoP+kUsSyXSYcUxsgW+fx5FW9
+22Ya3HNWnWxMCSfSGGlTFXGj2whf25SkL25dM9iblO4ZOx4MX8kaXij7TaYy8hMM
+Vf6/OMnXqtPKho+ctZZVKZkE9PxdS4f/pnp5EsdoOZwNBtfQ1WqVLWd3DlGWhnsH
+7L8ZSP2HkoI4Pd1wtkpOKZc+yM2bFXWa8WY4TcmpUQKBgQD33HxGdtmtZehrexSA
+/ZwWJlMslUsNz4Ivv6s7J4WCRhdh94+r9TWQP/yHdT9Ry5bvn84I5ZLUdp+aA962
+mvjz+GIglkCGpA7HU/hqurB1O63pj2cIDB8qhV21zjVIoqXcQ7IBJ+tqD79nF8vm
+h3KfuHUhuu1rayGepbtIyNhLdwKBgQDLgw4TJBg/QB8RzYECk78QnfZpCExsQA/z
+YJpc+dF2/nsid5R2u9jWzfmgHM2Jjo2/+ofRUaTqcFYU0K57CqmQkOLIzsbNQoYt
+e2NOANNVHiZLuzTZC2r3BrrkNbo3YvQzhAesUA5lS6LfrxBLUKiwo2LU9NlmJs3b
+UPVFYI0/1QKBgCswxIcS1sOcam+wNtZzWuuRKhUuvrFdY3YmlBPuwxj8Vb7AgMya
+IgdM3xhLmgkKzPZchm6OcpOLSCxyWDDBuHfq5E6BYCUWGW0qeLNAbNdA2wFD99Qz
+KIskSjwP/sD1dql3MmF5L1CABf5U6zb0i0jBv8ds50o8lNMsVgJM3UPpAoGBAL1+
+nzllb4pdi1CJWKnspoizfQCZsIdPM0r71V/jYY36MO+MBtpz2NlSWzAiAaQm74gl
+oBdgfT2qMg0Zro11BSRONEykdOolGkj5TiMQk7b65s+3VeMPRZ8UTis2d9kgs5/Q
+PVDODkl1nwfGu1ZVmW04BUujXVZHpYCkJm1eFMetAoGAImE7gWj+qRMhpbtCCGCg
+z06gDKvMrF6S+GJsvUoSyM8oUtfdPodI6gWAC65NfYkIiqbpCaEVNzfui73f5Lnz
+p5X1IbzhuH5UZs/k5A3OR2PPDbPs3lqEw7YJdBdLVRmO1o824uaXaJJwkL/1C+lq
+8dh1wV3CnynNmZApkz4vpzQ=
+-----END PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/hitch/example/index.txt b/nixpkgs/nixos/tests/hitch/example/index.txt
new file mode 100644
index 000000000000..0478b1c26351
--- /dev/null
+++ b/nixpkgs/nixos/tests/hitch/example/index.txt
@@ -0,0 +1 @@
+We are all good!
diff --git a/nixpkgs/nixos/tests/hledger-web.nix b/nixpkgs/nixos/tests/hledger-web.nix
new file mode 100644
index 000000000000..f8919f7d4bd0
--- /dev/null
+++ b/nixpkgs/nixos/tests/hledger-web.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  journal = pkgs.writeText "test.journal" ''
+    2010/01/10 Loan
+        assets:cash                 500$
+        income:loan                -500$
+    2010/01/10 NixOS Foundation donation
+        expenses:donation           250$
+        assets:cash                -250$
+  '';
+in
+rec {
+  name = "hledger-web";
+  meta.maintainers = with lib.maintainers; [ marijanp ];
+
+  nodes = rec {
+    server = { config, pkgs, ... }: {
+      services.hledger-web = {
+        host = "127.0.0.1";
+        port = 5000;
+        enable = true;
+        capabilities.manage = true;
+      };
+      networking.firewall.allowedTCPPorts = [ config.services.hledger-web.port ];
+      systemd.services.hledger-web.preStart = ''
+        ln -s ${journal} /var/lib/hledger-web/.hledger.journal
+      '';
+    };
+    apiserver = { ... }: {
+      imports = [ server ];
+      services.hledger-web.serveApi = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("hledger-web.service")
+    server.wait_for_open_port(5000)
+    with subtest("Check if web UI is accessible"):
+        page = server.succeed("curl -L http://127.0.0.1:5000")
+        assert ".hledger.journal" in page
+
+    apiserver.wait_for_unit("hledger-web.service")
+    apiserver.wait_for_open_port(5000)
+    with subtest("Check if the JSON API is served"):
+        transactions = apiserver.succeed("curl -L http://127.0.0.1:5000/transactions")
+        assert "NixOS Foundation donation" in transactions
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hocker-fetchdocker/default.nix b/nixpkgs/nixos/tests/hocker-fetchdocker/default.nix
new file mode 100644
index 000000000000..b5c06126c2e8
--- /dev/null
+++ b/nixpkgs/nixos/tests/hocker-fetchdocker/default.nix
@@ -0,0 +1,16 @@
+import ../make-test-python.nix ({ pkgs, ...} : {
+  name = "test-hocker-fetchdocker";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ixmatus ];
+    broken = true; # tries to download from registry-1.docker.io - how did this ever work?
+  };
+
+  nodes.machine = import ./machine.nix;
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("sockets.target")
+    machine.wait_until_succeeds("docker run registry-1.docker.io/v2/library/hello-world:latest")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hocker-fetchdocker/hello-world-container.nix b/nixpkgs/nixos/tests/hocker-fetchdocker/hello-world-container.nix
new file mode 100644
index 000000000000..a127875264e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/hocker-fetchdocker/hello-world-container.nix
@@ -0,0 +1,19 @@
+{ fetchDockerConfig, fetchDockerLayer, fetchdocker }:
+fetchdocker rec {
+    name = "hello-world";
+    registry = "https://registry-1.docker.io/v2/";
+    repository = "library";
+    imageName = "hello-world";
+    tag = "latest";
+    imageConfig = fetchDockerConfig {
+      inherit tag registry repository imageName;
+      sha256 = "1ivbd23hyindkahzfw4kahgzi6ibzz2ablmgsz6340vc6qr1gagj";
+    };
+    imageLayers = let
+      layer0 = fetchDockerLayer {
+        inherit registry repository imageName;
+        layerDigest = "ca4f61b1923c10e9eb81228bd46bee1dfba02b9c7dac1844527a734752688ede";
+        sha256 = "1plfd194fwvsa921ib3xkhms1yqxxrmx92r2h7myj41wjaqn2kya";
+      };
+      in [ layer0 ];
+  }
diff --git a/nixpkgs/nixos/tests/hocker-fetchdocker/machine.nix b/nixpkgs/nixos/tests/hocker-fetchdocker/machine.nix
new file mode 100644
index 000000000000..885adebe1498
--- /dev/null
+++ b/nixpkgs/nixos/tests/hocker-fetchdocker/machine.nix
@@ -0,0 +1,26 @@
+{ pkgs, ... }:
+{ nixpkgs.config.packageOverrides = pkgs': {
+    hello-world-container = pkgs'.callPackage ./hello-world-container.nix { };
+  };
+
+  virtualisation.docker = {
+    enable  = true;
+    package = pkgs.docker;
+  };
+
+  systemd.services.docker-load-fetchdocker-image = {
+    description = "Docker load hello-world-container";
+    wantedBy    = [ "multi-user.target" ];
+    wants       = [ "docker.service" ];
+    after       = [ "docker.service" ];
+
+    script = ''
+      ${pkgs.hello-world-container}/compositeImage.sh | ${pkgs.docker}/bin/docker load
+    '';
+
+    serviceConfig = {
+      Type = "oneshot";
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/tests/hockeypuck.nix b/nixpkgs/nixos/tests/hockeypuck.nix
new file mode 100644
index 000000000000..675d6b226ad2
--- /dev/null
+++ b/nixpkgs/nixos/tests/hockeypuck.nix
@@ -0,0 +1,63 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+let
+  gpgKeyring = (pkgs.runCommand "gpg-keyring" { buildInputs = [ pkgs.gnupg ]; } ''
+    mkdir -p $out
+    export GNUPGHOME=$out
+    cat > foo <<EOF
+      %echo Generating a basic OpenPGP key
+      %no-protection
+      Key-Type: DSA
+      Key-Length: 1024
+      Subkey-Type: ELG-E
+      Subkey-Length: 1024
+      Name-Real: Foo Example
+      Name-Email: foo@example.org
+      Expire-Date: 0
+      # Do a commit here, so that we can later print "done"
+      %commit
+      %echo done
+    EOF
+    gpg --batch --generate-key foo
+    rm $out/S.gpg-agent $out/S.gpg-agent.*
+  '');
+in {
+  name = "hockeypuck";
+  meta.maintainers = with lib.maintainers; [ etu ];
+
+  nodes.machine = { ... }: {
+    # Used for test
+    environment.systemPackages = [ pkgs.gnupg ];
+
+    services.hockeypuck.enable = true;
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "hockeypuck" ];
+      ensureUsers = [{
+        name = "hockeypuck";
+        ensureDBOwnership = true;
+      }];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("hockeypuck.service")
+    machine.wait_for_open_port(11371)
+
+    response = machine.succeed("curl -vvv -s http://127.0.0.1:11371/")
+
+    assert "<title>OpenPGP Keyserver</title>" in response, "HTML title not found"
+
+    # Copy the keyring
+    machine.succeed("cp -R ${gpgKeyring} /tmp/GNUPGHOME")
+
+    # Extract our GPG key id
+    keyId = machine.succeed("GNUPGHOME=/tmp/GNUPGHOME gpg --list-keys | grep dsa1024 --after-context=1 | grep -v dsa1024").strip()
+
+    # Send the key to our local keyserver
+    machine.succeed("GNUPGHOME=/tmp/GNUPGHOME gpg --keyserver hkp://127.0.0.1:11371 --send-keys " + keyId)
+
+    # Receive the key from our local keyserver to a separate directory
+    machine.succeed("GNUPGHOME=$(mktemp -d) gpg --keyserver hkp://127.0.0.1:11371 --recv-keys " + keyId)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/home-assistant.nix b/nixpkgs/nixos/tests/home-assistant.nix
new file mode 100644
index 000000000000..05fb2fa1e06a
--- /dev/null
+++ b/nixpkgs/nixos/tests/home-assistant.nix
@@ -0,0 +1,241 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  configDir = "/var/lib/foobar";
+in {
+  name = "home-assistant";
+  meta.maintainers = lib.teams.home-assistant.members;
+
+  nodes.hass = { pkgs, ... }: {
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "hass" ];
+      ensureUsers = [{
+        name = "hass";
+        ensureDBOwnership = true;
+      }];
+    };
+
+    services.home-assistant = {
+      enable = true;
+      inherit configDir;
+
+      # provide dependencies through package overrides
+      package = (pkgs.home-assistant.override {
+        extraPackages = ps: with ps; [
+          colorama
+        ];
+        extraComponents = [
+          # test char-tty device allow propagation into the service
+          "zha"
+         ];
+      });
+
+      # provide component dependencies explicitly from the module
+      extraComponents = [
+        "mqtt"
+      ];
+
+      # provide package for postgresql support
+      extraPackages = python3Packages: with python3Packages; [
+        psycopg2
+      ];
+
+      # test loading custom components
+      customComponents = with pkgs.home-assistant-custom-components; [
+        prometheus_sensor
+      ];
+
+      # test loading lovelace modules
+      customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
+        mini-graph-card
+      ];
+
+      config = {
+        homeassistant = {
+          name = "Home";
+          time_zone = "UTC";
+          latitude = "0.0";
+          longitude = "0.0";
+          elevation = 0;
+        };
+
+        # configure the recorder component to use the postgresql db
+        recorder.db_url = "postgresql://@/hass";
+
+        # we can't load default_config, because the updater requires
+        # network access and would cause an error, so load frontend
+        # here explicitly.
+        # https://www.home-assistant.io/integrations/frontend/
+        frontend = {};
+
+        # include some popular integrations, that absolutely shouldn't break
+        knx = {};
+        shelly = {};
+        zha = {};
+
+        # set up a wake-on-lan switch to test capset capability required
+        # for the ping suid wrapper
+        # https://www.home-assistant.io/integrations/wake_on_lan/
+        switch = [ {
+          platform = "wake_on_lan";
+          mac = "00:11:22:33:44:55";
+          host = "127.0.0.1";
+        } ];
+
+        # test component-based capability assignment (CAP_NET_BIND_SERVICE)
+        # https://www.home-assistant.io/integrations/emulated_hue/
+        emulated_hue = {
+          host_ip = "127.0.0.1";
+          listen_port = 80;
+        };
+
+        # https://www.home-assistant.io/integrations/logger/
+        logger = {
+          default = "info";
+        };
+      };
+
+      # configure the sample lovelace dashboard
+      lovelaceConfig = {
+        title = "My Awesome Home";
+        views = [{
+          title = "Example";
+          cards = [{
+            type = "markdown";
+            title = "Lovelace";
+            content = "Welcome to your **Lovelace UI**.";
+          }];
+        }];
+      };
+      lovelaceConfigWritable = true;
+    };
+
+    # Cause a configuration change inside `configuration.yml` and verify that the process is being reloaded.
+    specialisation.differentName = {
+      inheritParentConfig = true;
+      configuration.services.home-assistant.config.homeassistant.name = lib.mkForce "Test Home";
+    };
+
+    # Cause a configuration change that requires a service restart as we added a new runtime dependency
+    specialisation.newFeature = {
+      inheritParentConfig = true;
+      configuration.services.home-assistant.config.backup = {};
+    };
+
+    specialisation.removeCustomThings = {
+      inheritParentConfig = true;
+      configuration.services.home-assistant = {
+        customComponents = lib.mkForce [];
+        customLovelaceModules = lib.mkForce [];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    system = nodes.hass.system.build.toplevel;
+  in
+  ''
+    import json
+
+    start_all()
+
+
+    def get_journal_cursor() -> str:
+        exit, out = hass.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR")
+        assert exit == 0
+        return json.loads(out)["__CURSOR"]
+
+
+    def get_journal_since(cursor) -> str:
+        exit, out = hass.execute(f"journalctl --after-cursor='{cursor}' -u home-assistant.service")
+        assert exit == 0
+        return out
+
+
+    def get_unit_property(property) -> str:
+        exit, out = hass.execute(f"systemctl show --property={property} home-assistant.service")
+        assert exit == 0
+        return out
+
+
+    def wait_for_homeassistant(cursor):
+        hass.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'")
+
+
+    hass.wait_for_unit("home-assistant.service")
+    cursor = get_journal_cursor()
+
+    with subtest("Check that YAML configuration file is in place"):
+        hass.succeed("test -L ${configDir}/configuration.yaml")
+
+    with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"):
+        hass.succeed("test -f ${configDir}/ui-lovelace.yaml")
+
+    with subtest("Check that Home Assistant's web interface and API can be reached"):
+        wait_for_homeassistant(cursor)
+        hass.wait_for_open_port(8123)
+        hass.succeed("curl --fail http://localhost:8123/lovelace")
+
+    with subtest("Check that custom components get installed"):
+        hass.succeed("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
+        hass.wait_until_succeeds("journalctl -u home-assistant.service | grep -q 'We found a custom integration prometheus_sensor which has not been tested by Home Assistant'")
+
+    with subtest("Check that lovelace modules are referenced and fetchable"):
+        hass.succeed("grep -q 'mini-graph-card-bundle.js' '${configDir}/configuration.yaml'")
+        hass.succeed("curl --fail http://localhost:8123/local/nixos-lovelace-modules/mini-graph-card-bundle.js")
+
+    with subtest("Check that optional dependencies are in the PYTHONPATH"):
+        env = get_unit_property("Environment")
+        python_path = env.split("PYTHONPATH=")[1].split()[0]
+        for package in ["colorama", "paho-mqtt", "psycopg2"]:
+            assert package in python_path, f"{package} not in PYTHONPATH"
+
+    with subtest("Check that declaratively configured components get setup"):
+        journal = get_journal_since(cursor)
+        for domain in ["emulated_hue", "wake_on_lan"]:
+            assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
+
+    with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"):
+        hass.wait_for_open_port(80)
+        hass.succeed("curl --fail http://localhost:80/description.xml")
+
+    with subtest("Check extra components are considered in systemd unit hardening"):
+        hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB")
+
+    with subtest("Check service reloads when configuration changes"):
+        pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test")
+        new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        assert pid == new_pid, "The PID of the process should not change between process reloads"
+        wait_for_homeassistant(cursor)
+
+    with subtest("Check service restarts when dependencies change"):
+        pid = new_pid
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test")
+        new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service")
+        assert pid != new_pid, "The PID of the process should change when its PYTHONPATH changess"
+        wait_for_homeassistant(cursor)
+
+    with subtest("Check that new components get setup after restart"):
+        journal = get_journal_since(cursor)
+        for domain in ["backup"]:
+            assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
+
+    with subtest("Check custom components and custom lovelace modules get removed"):
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/removeCustomThings/bin/switch-to-configuration test")
+        hass.fail("grep -q 'mini-graph-card-bundle.js' '${configDir}/ui-lovelace.yaml'")
+        hass.fail("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
+        wait_for_homeassistant(cursor)
+
+    with subtest("Check that no errors were logged"):
+        hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR")
+
+    with subtest("Check systemd unit hardening"):
+        hass.log(hass.succeed("systemctl cat home-assistant.service"))
+        hass.log(hass.succeed("systemd-analyze security home-assistant.service"))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/homepage-dashboard.nix b/nixpkgs/nixos/tests/homepage-dashboard.nix
new file mode 100644
index 000000000000..56e077f5ff6d
--- /dev/null
+++ b/nixpkgs/nixos/tests/homepage-dashboard.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "homepage-dashboard";
+  meta.maintainers = with lib.maintainers; [ jnsgruk ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.homepage-dashboard.enable = true;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("homepage-dashboard.service")
+    machine.wait_for_open_port(8082)
+    machine.succeed("curl --fail http://localhost:8082/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/honk.nix b/nixpkgs/nixos/tests/honk.nix
new file mode 100644
index 000000000000..71d86a592439
--- /dev/null
+++ b/nixpkgs/nixos/tests/honk.nix
@@ -0,0 +1,32 @@
+{ lib, ... }:
+
+{
+  name = "honk-server";
+
+  nodes = {
+    machine = { pkgs, ... }: {
+      services.honk = {
+        enable = true;
+        host = "0.0.0.0";
+        port = 8080;
+        username = "username";
+        passwordFile = "${pkgs.writeText "honk-password" "secure"}";
+        servername = "servername";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("honk.service")
+    machine.wait_for_open_port(8080)
+
+    machine.stop_job("honk")
+    machine.wait_for_closed_port(8080)
+
+    machine.start_job("honk")
+    machine.wait_for_open_port(8080)
+  '';
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+}
diff --git a/nixpkgs/nixos/tests/hostname.nix b/nixpkgs/nixos/tests/hostname.nix
new file mode 100644
index 000000000000..dffec956bc0b
--- /dev/null
+++ b/nixpkgs/nixos/tests/hostname.nix
@@ -0,0 +1,73 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  makeHostNameTest = hostName: domain: fqdnOrNull:
+    let
+      fqdn = hostName + (optionalString (domain != null) ".${domain}");
+      getStr = str: # maybeString2String
+        let res = builtins.tryEval str;
+        in if (res.success && res.value != null) then res.value else "null";
+    in
+    makeTest {
+      name = "hostname-${fqdn}";
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ primeos blitz ];
+      };
+
+      nodes.machine = { lib, ... }: {
+        networking.hostName = hostName;
+        networking.domain = domain;
+
+        environment.systemPackages = with pkgs; [
+          inetutils
+        ];
+      };
+
+      testScript = { nodes, ... }: ''
+        start_all()
+
+        machine = ${hostName}
+
+        machine.systemctl("start network-online.target")
+        machine.wait_for_unit("network-online.target")
+
+        # Test if NixOS computes the correct FQDN (either a FQDN or an error/null):
+        assert "${getStr nodes.machine.networking.fqdn}" == "${getStr fqdnOrNull}"
+
+        # The FQDN, domain name, and hostname detection should work as expected:
+        assert "${fqdn}" == machine.succeed("hostname --fqdn").strip()
+        assert "${optionalString (domain != null) domain}" == machine.succeed("dnsdomainname").strip()
+        assert (
+            "${hostName}"
+            == machine.succeed(
+                'hostnamectl status | grep "Static hostname" | cut -d: -f2'
+            ).strip()
+        )
+
+        # 127.0.0.1 and ::1 should resolve back to "localhost":
+        assert (
+            "localhost" == machine.succeed("getent hosts 127.0.0.1 | awk '{print $2}'").strip()
+        )
+        assert "localhost" == machine.succeed("getent hosts ::1 | awk '{print $2}'").strip()
+
+        # 127.0.0.2 should resolve back to the FQDN and hostname:
+        fqdn_and_host_name = "${optionalString (domain != null) "${hostName}.${domain} "}${hostName}"
+        assert (
+            fqdn_and_host_name
+            == machine.succeed("getent hosts 127.0.0.2 | awk '{print $2,$3}'").strip()
+        )
+      '';
+    };
+
+in
+{
+  noExplicitDomain = makeHostNameTest "ahost" null null;
+
+  explicitDomain = makeHostNameTest "ahost" "adomain" "ahost.adomain";
+}
diff --git a/nixpkgs/nixos/tests/hound.nix b/nixpkgs/nixos/tests/hound.nix
new file mode 100644
index 000000000000..a9b036deb0dd
--- /dev/null
+++ b/nixpkgs/nixos/tests/hound.nix
@@ -0,0 +1,59 @@
+# Test whether `houndd` indexes nixpkgs
+import ./make-test-python.nix ({ pkgs, ... } : {
+  name = "hound";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ grahamc ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    services.hound = {
+      enable = true;
+      config = ''
+        {
+          "max-concurrent-indexers": 1,
+          "dbpath": "/var/lib/hound/data",
+          "repos": {
+            "nix": {
+              "url": "file:///var/lib/hound/my-git"
+            }
+          }
+        }
+      '';
+    };
+
+    systemd.services.houndseed = {
+      description = "seed hound with a git repo";
+      requiredBy = [ "hound.service" ];
+      before = [ "hound.service" ];
+
+      serviceConfig = {
+        User = "hound";
+        Group = "hound";
+        WorkingDirectory = "/var/lib/hound";
+      };
+      path = [ pkgs.git ];
+      script = ''
+        git config --global user.email "you@example.com"
+        git config --global user.name "Your Name"
+        git init my-git --bare
+        git init my-git-clone
+        cd my-git-clone
+        echo 'hi nix!' > hello
+        git add hello
+        git commit -m "hello there :)"
+        git remote add origin /var/lib/hound/my-git
+        git push origin master
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("network.target")
+    machine.wait_for_unit("hound.service")
+    machine.wait_for_open_port(6080)
+    machine.wait_until_succeeds(
+        "curl -f http://127.0.0.1:6080/api/v1/search\?stats\=fosho\&repos\=\*\&rng=%3A20\&q\=hi\&files\=\&i=nope | grep 'Filename' | grep 'hello'"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/hydra/common.nix b/nixpkgs/nixos/tests/hydra/common.nix
new file mode 100644
index 000000000000..2bce03418e1f
--- /dev/null
+++ b/nixpkgs/nixos/tests/hydra/common.nix
@@ -0,0 +1,48 @@
+{ system, ... }:
+{
+  baseConfig = { pkgs, ... }: let
+    trivialJob = pkgs.writeTextDir "trivial.nix" ''
+     { trivial = builtins.derivation {
+         name = "trivial";
+         system = "${system}";
+         builder = "/bin/sh";
+         allowSubstitutes = false;
+         preferLocalBuild = true;
+         args = ["-c" "echo success > $out; exit 0"];
+       };
+     }
+    '';
+
+    createTrivialProject = pkgs.stdenv.mkDerivation {
+      name = "create-trivial-project";
+      dontUnpack = true;
+      nativeBuildInputs = [ pkgs.makeWrapper ];
+      installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh";
+      postFixup = ''
+        wrapProgram "$out/bin/create-trivial-project.sh" --prefix PATH ":" ${pkgs.lib.makeBinPath [ pkgs.curl ]} --set EXPR_PATH ${trivialJob}
+      '';
+    };
+  in {
+    virtualisation.memorySize = 2048;
+    time.timeZone = "UTC";
+    environment.systemPackages = [ createTrivialProject pkgs.jq ];
+    services.hydra = {
+      enable = true;
+      # Hydra needs those settings to start up, so we add something not harmfull.
+      hydraURL = "example.com";
+      notificationSender = "example@example.com";
+      extraConfig = ''
+        email_notification = 1
+      '';
+    };
+    services.postfix.enable = true;
+    nix = {
+      distributedBuilds = true;
+      buildMachines = [{
+        hostName = "localhost";
+        systems = [ system ];
+      }];
+      settings.substituters = [];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/hydra/create-trivial-project.sh b/nixpkgs/nixos/tests/hydra/create-trivial-project.sh
new file mode 100755
index 000000000000..5aae2d5bf90d
--- /dev/null
+++ b/nixpkgs/nixos/tests/hydra/create-trivial-project.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+#
+# This script creates a project, a jobset with an input of type local
+# path. This local path is a directory that contains a Nix expression
+# to define a job.
+# The EXPR-PATH environment variable must be set with the local path.
+
+set -e
+
+URL=http://localhost:3000
+USERNAME="admin"
+PASSWORD="admin"
+PROJECT_NAME="trivial"
+JOBSET_NAME="trivial"
+EXPR_PATH=${EXPR_PATH:-}
+
+if [ -z $EXPR_PATH ]; then
+   echo "Environment variable EXPR_PATH must be set"
+   exit 1
+fi
+
+mycurl() {
+  curl --referer $URL -H "Accept: application/json" -H "Content-Type: application/json" $@
+}
+
+cat >data.json <<EOF
+{ "username": "$USERNAME", "password": "$PASSWORD" }
+EOF
+mycurl -X POST -d '@data.json' $URL/login -c hydra-cookie.txt
+
+cat >data.json <<EOF
+{
+  "displayname":"Trivial",
+  "enabled":"1",
+  "visible":"1"
+}
+EOF
+mycurl --silent -X PUT $URL/project/$PROJECT_NAME -d @data.json -b hydra-cookie.txt
+
+cat >data.json <<EOF
+{
+  "description": "Trivial",
+  "checkinterval": "60",
+  "enabled": "1",
+  "visible": "1",
+  "keepnr": "1",
+  "enableemail": true,
+  "emailoverride": "hydra@localhost",
+  "nixexprinput": "trivial",
+  "nixexprpath": "trivial.nix",
+  "inputs": {
+    "trivial": {
+      "value": "$EXPR_PATH",
+      "type": "path"
+    }
+  }
+}
+EOF
+mycurl --silent -X PUT $URL/jobset/$PROJECT_NAME/$JOBSET_NAME -d @data.json -b hydra-cookie.txt
diff --git a/nixpkgs/nixos/tests/hydra/default.nix b/nixpkgs/nixos/tests/hydra/default.nix
new file mode 100644
index 000000000000..98c3c6fbae9f
--- /dev/null
+++ b/nixpkgs/nixos/tests/hydra/default.nix
@@ -0,0 +1,59 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+
+with import ../../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+
+  inherit (import ./common.nix { inherit system; }) baseConfig;
+
+  hydraPkgs = {
+    inherit (pkgs) hydra_unstable;
+  };
+
+  makeHydraTest = with pkgs.lib; name: package: makeTest {
+    name = "hydra-${name}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ lewo ];
+    };
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ baseConfig ];
+      services.hydra = { inherit package; };
+    };
+
+    testScript = ''
+      # let the system boot up
+      machine.wait_for_unit("multi-user.target")
+      # test whether the database is running
+      machine.wait_for_unit("postgresql.service")
+      # test whether the actual hydra daemons are running
+      machine.wait_for_unit("hydra-init.service")
+      machine.require_unit_state("hydra-queue-runner.service")
+      machine.require_unit_state("hydra-evaluator.service")
+      machine.require_unit_state("hydra-notify.service")
+
+      machine.succeed("hydra-create-user admin --role admin --password admin")
+
+      # create a project with a trivial job
+      machine.wait_for_open_port(3000)
+
+      # make sure the build as been successfully built
+      machine.succeed("create-trivial-project.sh")
+
+      machine.wait_until_succeeds(
+          'curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" |  jq .buildstatus | xargs test 0 -eq'
+      )
+
+      machine.wait_until_succeeds(
+          'journalctl -eu hydra-notify.service -o cat | grep -q "sending mail notification to hydra@localhost"'
+      )
+    '';
+  };
+
+in
+
+mapAttrs makeHydraTest hydraPkgs
diff --git a/nixpkgs/nixos/tests/i3wm.nix b/nixpkgs/nixos/tests/i3wm.nix
new file mode 100644
index 000000000000..b216650d8192
--- /dev/null
+++ b/nixpkgs/nixos/tests/i3wm.nix
@@ -0,0 +1,46 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "i3wm";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  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";
+    services.xserver.windowManager.i3.enable = true;
+  };
+
+  testScript = { ... }: ''
+    with subtest("ensure x starts"):
+        machine.wait_for_x()
+        machine.wait_for_file("/home/alice/.Xauthority")
+        machine.succeed("xauth merge ~alice/.Xauthority")
+
+    with subtest("ensure we get first configuration window"):
+        machine.wait_for_window(r".*?first configuration.*?")
+        machine.sleep(2)
+        machine.screenshot("started")
+
+    with subtest("ensure we generate and save a config"):
+        # press return to indicate we want to gen a new config
+        machine.send_key("\n")
+        machine.sleep(2)
+        machine.screenshot("preconfig")
+        # press alt then return to indicate we want to use alt as our Mod key
+        machine.send_key("alt")
+        machine.send_key("\n")
+        machine.sleep(2)
+        # make sure the config file is created before we continue
+        machine.wait_for_file("/home/alice/.config/i3/config")
+        machine.screenshot("postconfig")
+        machine.sleep(2)
+
+    with subtest("ensure we can open a new terminal"):
+        machine.send_key("alt-ret")
+        machine.sleep(2)
+        machine.wait_for_window(r"alice.*?machine")
+        machine.sleep(2)
+        machine.screenshot("terminal")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/icingaweb2.nix b/nixpkgs/nixos/tests/icingaweb2.nix
new file mode 100644
index 000000000000..e631e667bd50
--- /dev/null
+++ b/nixpkgs/nixos/tests/icingaweb2.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "icingaweb2";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ das_j ];
+  };
+
+  nodes = {
+    icingaweb2 = { config, pkgs, ... }: {
+      services.icingaweb2 = {
+        enable = true;
+
+        modulePackages = with pkgs.icingaweb2Modules; {
+          particles = theme-particles;
+          spring = theme-spring;
+        };
+
+        modules = {
+          doc.enable = true;
+          migrate.enable =  true;
+          setup.enable = true;
+          test.enable = true;
+          translation.enable = true;
+        };
+
+        generalConfig = {
+          global = {
+            module_path = "${pkgs.icingaweb2}/modules";
+          };
+        };
+
+        authentications = {
+          icingaweb = {
+            backend = "external";
+          };
+        };
+
+        groupBackends = {
+          icingaweb = {
+            backend = "db";
+            resource = "icingaweb_db";
+          };
+        };
+
+        resources = {
+          # Not used, so no DB server needed
+          icingaweb_db = {
+            type = "db";
+            db = "mysql";
+            host = "localhost";
+            username = "icingaweb2";
+            password = "icingaweb2";
+            dbname = "icingaweb2";
+          };
+        };
+
+        roles = {
+          Administrators = {
+            users = "*";
+            permissions = "*";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    icingaweb2.wait_for_unit("multi-user.target")
+    icingaweb2.succeed("curl -sSf http://icingaweb2/authentication/login")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/iftop.nix b/nixpkgs/nixos/tests/iftop.nix
new file mode 100644
index 000000000000..933f115a8a5a
--- /dev/null
+++ b/nixpkgs/nixos/tests/iftop.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "iftop";
+  meta.maintainers = with lib.maintainers; [ ma27 ];
+
+  nodes = {
+    withIftop = {
+      imports = [ ./common/user-account.nix ];
+      programs.iftop.enable = true;
+    };
+    withoutIftop = {
+      imports = [ ./common/user-account.nix ];
+      environment.systemPackages = [ pkgs.iftop ];
+    };
+  };
+
+  testScript = ''
+    with subtest("machine with iftop enabled"):
+        withIftop.wait_for_unit("default.target")
+        # limit to eth1 (eth0 is the test driver's control interface)
+        # and don't try name lookups
+        withIftop.succeed("su -l alice -c 'iftop -t -s 1 -n -i eth1'")
+
+    with subtest("machine without iftop"):
+        withoutIftop.wait_for_unit("default.target")
+        # check that iftop is there but user alice lacks capabilitie
+        withoutIftop.succeed("iftop -t -s 1 -n -i eth1")
+        withoutIftop.fail("su -l alice -c 'iftop -t -s 1 -n -i eth1'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/image-contents.nix b/nixpkgs/nixos/tests/image-contents.nix
new file mode 100644
index 000000000000..858f7d8c68f4
--- /dev/null
+++ b/nixpkgs/nixos/tests/image-contents.nix
@@ -0,0 +1,62 @@
+# Tests the contents attribute of nixos/lib/make-disk-image.nix
+# including its user, group, and mode attributes.
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+with import common/ec2.nix { inherit makeTest pkgs; };
+
+let
+  config = (import ../lib/eval-config.nix {
+    inherit system;
+    modules = [
+      ../modules/testing/test-instrumentation.nix
+      ../modules/profiles/qemu-guest.nix
+      {
+        fileSystems."/".device = "/dev/disk/by-label/nixos";
+        boot.loader.grub.device = "/dev/vda";
+        boot.loader.timeout = 0;
+      }
+    ];
+  }).config;
+  image = (import ../lib/make-disk-image.nix {
+    inherit pkgs config;
+    lib = pkgs.lib;
+    format = "qcow2";
+    contents = [
+      {
+        source = pkgs.writeText "testFile" "contents";
+        target = "/testFile";
+        user = "1234";
+        group = "5678";
+        mode = "755";
+      }
+      {
+        source = ./.;
+        target = "/testDir";
+      }
+    ];
+  }) + "/nixos.qcow2";
+
+in makeEc2Test {
+  name = "image-contents";
+  inherit image;
+  userData = null;
+  script = ''
+    machine.start()
+    # Test that if contents includes a file, it is copied to the target.
+    assert "content" in machine.succeed("cat /testFile")
+    fileDetails = machine.succeed("ls -l /testFile")
+    assert "1234" in fileDetails
+    assert "5678" in fileDetails
+    assert "rwxr-xr-x" in fileDetails
+
+    # Test that if contents includes a directory, it is copied to the target.
+    dirList = machine.succeed("ls /testDir")
+    assert "image-contents.nix" in dirList
+  '';
+}
diff --git a/nixpkgs/nixos/tests/incron.nix b/nixpkgs/nixos/tests/incron.nix
new file mode 100644
index 000000000000..d016360ba0ef
--- /dev/null
+++ b/nixpkgs/nixos/tests/incron.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "incron";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  nodes.machine =
+    { ... }:
+    { services.incron.enable = true;
+      services.incron.extraPackages = [ pkgs.coreutils ];
+      services.incron.systab = ''
+        /test IN_CREATE,IN_MODIFY,IN_CLOSE_WRITE,IN_MOVED_FROM,IN_MOVED_TO echo "$@/$# $%" >> /root/incron.log
+      '';
+
+      # ensure the directory to be monitored exists before incron is started
+      systemd.tmpfiles.settings.incron-test = {
+        "/test".d = { };
+      };
+    };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("incron.service")
+
+    machine.succeed("test -d /test")
+    # create some activity for incron to monitor
+    machine.succeed("touch /test/file")
+    machine.succeed("echo foo >> /test/file")
+    machine.succeed("mv /test/file /root")
+    machine.succeed("mv /root/file /test")
+
+    machine.sleep(1)
+
+    # touch /test/file
+    machine.succeed("grep '/test/file IN_CREATE' /root/incron.log")
+
+    # echo foo >> /test/file
+    machine.succeed("grep '/test/file IN_MODIFY' /root/incron.log")
+    machine.succeed("grep '/test/file IN_CLOSE_WRITE' /root/incron.log")
+
+    # mv /test/file /root
+    machine.succeed("grep '/test/file IN_MOVED_FROM' /root/incron.log")
+
+    # mv /root/file /test
+    machine.succeed("grep '/test/file IN_MOVED_TO' /root/incron.log")
+
+    # ensure something unexpected is not present
+    machine.fail("grep 'IN_OPEN' /root/incron.log")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/incus/container.nix b/nixpkgs/nixos/tests/incus/container.nix
new file mode 100644
index 000000000000..0f42d16f133d
--- /dev/null
+++ b/nixpkgs/nixos/tests/incus/container.nix
@@ -0,0 +1,105 @@
+import ../make-test-python.nix ({ pkgs, lib, extra ? {}, ... } :
+
+let
+  releases = import ../../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+    } // extra;
+  };
+
+  container-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+  container-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+in
+{
+  name = "incus-container";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { ... }: {
+    virtualisation = {
+      # Ensure test VM has enough resources for creating and managing guests
+      cores = 2;
+      memorySize = 1024;
+      diskSize = 4096;
+
+      incus.enable = true;
+    };
+  };
+
+  testScript = ''
+    def instance_is_up(_) -> bool:
+        status, _ = machine.execute("incus exec container --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
+        return status == 0
+
+    def set_container(config):
+        machine.succeed(f"incus config set container {config}")
+        machine.succeed("incus restart container")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+    machine.wait_for_unit("incus.service")
+
+    # no preseed should mean no service
+    machine.fail("systemctl status incus-preseed.service")
+
+    machine.succeed("incus admin init --minimal")
+
+    with subtest("Container image can be imported"):
+        machine.succeed("incus image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs}/*/*.tar.xz --alias nixos")
+
+    with subtest("Container can be launched and managed"):
+        machine.succeed("incus launch nixos container")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+        machine.succeed("echo true | incus exec container /run/current-system/sw/bin/bash -")
+
+    with subtest("Container mounts lxcfs overlays"):
+        machine.succeed("incus exec container mount | grep 'lxcfs on /proc/cpuinfo type fuse.lxcfs'")
+        machine.succeed("incus exec container mount | grep 'lxcfs on /proc/meminfo type fuse.lxcfs'")
+
+    with subtest("Container CPU limits can be managed"):
+        set_container("limits.cpu 1")
+        cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
+        assert cpuinfo == "1", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 1, got: {cpuinfo}"
+
+        set_container("limits.cpu 2")
+        cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
+        assert cpuinfo == "2", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 2, got: {cpuinfo}"
+
+    with subtest("Container memory limits can be managed"):
+        set_container("limits.memory 64MB")
+        meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
+        meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
+        assert meminfo_bytes == "62500 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '62500 kB', got: '{meminfo_bytes}'"
+
+        set_container("limits.memory 128MB")
+        meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
+        meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
+        assert meminfo_bytes == "125000 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '125000 kB', got: '{meminfo_bytes}'"
+
+    with subtest("lxc-container generator configures plain container"):
+        # reuse the existing container to save some time
+        machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+
+    with subtest("lxc-container generator configures nested container"):
+        machine.execute("incus delete --force container")
+        machine.succeed("incus launch nixos container --config security.nesting=true")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+        machine.fail("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+        target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip()
+        assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service"
+
+    with subtest("lxc-container generator configures privileged container"):
+        machine.execute("incus delete --force container")
+        machine.succeed("incus launch nixos container --config security.privileged=true")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+        machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/incus/default.nix b/nixpkgs/nixos/tests/incus/default.nix
new file mode 100644
index 000000000000..ff36fe9d6730
--- /dev/null
+++ b/nixpkgs/nixos/tests/incus/default.nix
@@ -0,0 +1,18 @@
+{
+  system ? builtins.currentSystem,
+  config ? { },
+  pkgs ? import ../../.. { inherit system config; },
+  handleTestOn,
+}:
+{
+  container-old-init = import ./container.nix { inherit system pkgs; };
+  container-new-init = import ./container.nix { inherit system pkgs; extra = {
+    # Enable new systemd init
+    boot.initrd.systemd.enable = true;
+  }; };
+  lxd-to-incus = import ./lxd-to-incus.nix { inherit system pkgs; };
+  preseed = import ./preseed.nix { inherit system pkgs; };
+  socket-activated = import ./socket-activated.nix { inherit system pkgs; };
+  ui = import ./ui.nix {inherit system pkgs;};
+  virtual-machine = handleTestOn [ "x86_64-linux" ] ./virtual-machine.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/incus/lxd-to-incus.nix b/nixpkgs/nixos/tests/incus/lxd-to-incus.nix
new file mode 100644
index 000000000000..c0fc98c224df
--- /dev/null
+++ b/nixpkgs/nixos/tests/incus/lxd-to-incus.nix
@@ -0,0 +1,110 @@
+import ../make-test-python.nix (
+
+  { pkgs, lib, ... }:
+
+  let
+    releases = import ../../release.nix { configuration.documentation.enable = lib.mkForce false; };
+
+    container-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+    container-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+  in
+  {
+    name = "lxd-to-incus";
+
+    meta = {
+      maintainers = lib.teams.lxc.members;
+    };
+
+    nodes.machine =
+      { lib, ... }:
+      {
+        virtualisation = {
+          diskSize = 6144;
+          cores = 2;
+          memorySize = 2048;
+
+          lxd.enable = true;
+          lxd.preseed = {
+            networks = [
+              {
+                name = "nixostestbr0";
+                type = "bridge";
+                config = {
+                  "ipv4.address" = "10.0.100.1/24";
+                  "ipv4.nat" = "true";
+                };
+              }
+            ];
+            profiles = [
+              {
+                name = "default";
+                devices = {
+                  eth0 = {
+                    name = "eth0";
+                    network = "nixostestbr0";
+                    type = "nic";
+                  };
+                  root = {
+                    path = "/";
+                    pool = "nixostest_pool";
+                    size = "35GiB";
+                    type = "disk";
+                  };
+                };
+              }
+              {
+                name = "nixos_notdefault";
+                devices = { };
+              }
+            ];
+            storage_pools = [
+              {
+                name = "nixostest_pool";
+                driver = "dir";
+              }
+            ];
+          };
+
+          incus.enable = true;
+        };
+      };
+
+    testScript = ''
+      def lxd_wait_for_preseed(_) -> bool:
+        _, output = machine.systemctl("is-active lxd-preseed.service")
+        return ("inactive" in output)
+
+      def lxd_instance_is_up(_) -> bool:
+          status, _ = machine.execute("lxc exec container --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
+          return status == 0
+
+      def incus_instance_is_up(_) -> bool:
+          status, _ = machine.execute("incus exec container --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
+          return status == 0
+
+      with machine.nested("initialize lxd and resources"):
+        machine.wait_for_unit("sockets.target")
+        machine.wait_for_unit("lxd.service")
+        retry(lxd_wait_for_preseed)
+
+        machine.succeed("lxc image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs}/*/*.tar.xz --alias nixos")
+        machine.succeed("lxc launch nixos container")
+        retry(lxd_instance_is_up)
+
+      machine.wait_for_unit("incus.service")
+
+      with machine.nested("run migration"):
+          machine.succeed("lxd-to-incus --yes")
+
+      with machine.nested("verify resources migrated to incus"):
+          machine.succeed("incus config show container")
+          retry(incus_instance_is_up)
+          machine.succeed("incus exec container -- true")
+          machine.succeed("incus profile show default | grep nixostestbr0")
+          machine.succeed("incus profile show default | grep nixostest_pool")
+          machine.succeed("incus profile show nixos_notdefault")
+          machine.succeed("incus storage show nixostest_pool")
+          machine.succeed("incus network show nixostestbr0")
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/incus/preseed.nix b/nixpkgs/nixos/tests/incus/preseed.nix
new file mode 100644
index 000000000000..a488d71f3c92
--- /dev/null
+++ b/nixpkgs/nixos/tests/incus/preseed.nix
@@ -0,0 +1,62 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+{
+  name = "incus-preseed";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      incus.enable = true;
+
+      incus.preseed = {
+        networks = [
+          {
+            name = "nixostestbr0";
+            type = "bridge";
+            config = {
+              "ipv4.address" = "10.0.100.1/24";
+              "ipv4.nat" = "true";
+            };
+          }
+        ];
+        profiles = [
+          {
+            name = "nixostest_default";
+            devices = {
+              eth0 = {
+                name = "eth0";
+                network = "nixostestbr0";
+                type = "nic";
+              };
+              root = {
+                path = "/";
+                pool = "default";
+                size = "35GiB";
+                type = "disk";
+              };
+            };
+          }
+        ];
+        storage_pools = [
+          {
+            name = "nixostest_pool";
+            driver = "dir";
+          }
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("incus.service")
+    machine.wait_for_unit("incus-preseed.service")
+
+    with subtest("Verify preseed resources created"):
+      machine.succeed("incus profile show nixostest_default")
+      machine.succeed("incus network info nixostestbr0")
+      machine.succeed("incus storage show nixostest_pool")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/incus/socket-activated.nix b/nixpkgs/nixos/tests/incus/socket-activated.nix
new file mode 100644
index 000000000000..fca536b7054f
--- /dev/null
+++ b/nixpkgs/nixos/tests/incus/socket-activated.nix
@@ -0,0 +1,28 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+{
+  name = "incus-socket-activated";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      incus.enable = true;
+      incus.socketActivation = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("incus.socket")
+
+    # ensure service is not running by default
+    machine.fail("systemctl is-active incus.service")
+    machine.fail("systemctl is-active incus-preseed.service")
+
+    # access the socket and ensure the service starts
+    machine.succeed("incus list")
+    machine.wait_for_unit("incus.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/incus/ui.nix b/nixpkgs/nixos/tests/incus/ui.nix
new file mode 100644
index 000000000000..24ce1217d8df
--- /dev/null
+++ b/nixpkgs/nixos/tests/incus/ui.nix
@@ -0,0 +1,63 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "incus-ui";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      incus.enable = true;
+      incus.ui.enable = true;
+    };
+
+    environment.systemPackages =
+      let
+        seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
+          {
+            libraries = with pkgs.python3Packages; [ selenium ];
+          } ''
+          from selenium import webdriver
+          from selenium.webdriver.common.by import By
+          from selenium.webdriver.firefox.options import Options
+          from selenium.webdriver.support.ui import WebDriverWait
+
+          options = Options()
+          options.add_argument("--headless")
+          service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+
+          driver = webdriver.Firefox(options=options, service=service)
+          driver.implicitly_wait(10)
+          driver.get("https://localhost:8443/ui")
+
+          wait = WebDriverWait(driver, 60)
+
+          assert len(driver.find_elements(By.CLASS_NAME, "l-application")) > 0
+          assert len(driver.find_elements(By.CLASS_NAME, "l-navigation__drawer")) > 0
+
+          driver.close()
+        '';
+      in
+      with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
+  };
+
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("incus.service")
+    machine.wait_for_file("/var/lib/incus/unix.socket")
+
+    # Configure incus listen address
+    machine.succeed("incus config set core.https_address :8443")
+    machine.succeed("systemctl restart incus")
+
+    # Check that the INCUS_UI environment variable is populated in the systemd unit
+    machine.succeed("cat /etc/systemd/system/incus.service | grep 'INCUS_UI'")
+
+    # Ensure the endpoint returns an HTML page with 'Incus UI' in the title
+    machine.succeed("curl -kLs https://localhost:8443/ui | grep '<title>Incus UI</title>'")
+
+    # Ensure the application is actually rendered by the Javascript
+    machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/incus/virtual-machine.nix b/nixpkgs/nixos/tests/incus/virtual-machine.nix
new file mode 100644
index 000000000000..c76e4f448f2f
--- /dev/null
+++ b/nixpkgs/nixos/tests/incus/virtual-machine.nix
@@ -0,0 +1,60 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  releases = import ../../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+
+      # Our tests require `grep` & friends:
+      environment.systemPackages = with pkgs; [busybox];
+    };
+  };
+
+  vm-image-metadata = releases.lxdVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system};
+  vm-image-disk = releases.lxdVirtualMachineImage.${pkgs.stdenv.hostPlatform.system};
+
+  instance-name = "instance1";
+in
+{
+  name = "incus-virtual-machine";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = {...}: {
+    virtualisation = {
+      # Ensure test VM has enough resources for creating and managing guests
+      cores = 2;
+      memorySize = 1024;
+      diskSize = 4096;
+
+      incus.enable = true;
+    };
+  };
+
+  testScript = ''
+    def instance_is_up(_) -> bool:
+      status, _ = machine.execute("incus exec ${instance-name} --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
+      return status == 0
+
+    machine.wait_for_unit("incus.service")
+
+    machine.succeed("incus admin init --minimal")
+
+    with subtest("virtual-machine image can be imported"):
+        machine.succeed("incus image import ${vm-image-metadata}/*/*.tar.xz ${vm-image-disk}/nixos.qcow2 --alias nixos")
+
+    with subtest("virtual-machine can be launched and become available"):
+        machine.succeed("incus launch nixos ${instance-name} --vm --config limits.memory=512MB --config security.secureboot=false")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+    with subtest("lxd-agent is started"):
+        machine.succeed("incus exec ${instance-name} systemctl is-active lxd-agent")
+
+    with subtest("lxd-agent has a valid path"):
+        machine.succeed("incus exec ${instance-name} -- bash -c 'true'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/influxdb.nix b/nixpkgs/nixos/tests/influxdb.nix
new file mode 100644
index 000000000000..03026f8404be
--- /dev/null
+++ b/nixpkgs/nixos/tests/influxdb.nix
@@ -0,0 +1,40 @@
+# This test runs influxdb and checks if influxdb is up and running
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "influxdb";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    one = { ... }: {
+      services.influxdb.enable = true;
+      environment.systemPackages = [ pkgs.httpie ];
+    };
+  };
+
+  testScript = ''
+    import shlex
+
+    start_all()
+
+    one.wait_for_unit("influxdb.service")
+
+    # create database
+    one.succeed(
+        "curl -XPOST http://localhost:8086/query --data-urlencode 'q=CREATE DATABASE test'"
+    )
+
+    # write some points and run simple query
+    out = one.succeed(
+        "curl -XPOST 'http://localhost:8086/write?db=test' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000'"
+    )
+
+    qv = "SELECT value FROM cpu_load_short WHERE region='us-west'"
+    cmd = f'curl -GET "http://localhost:8086/query?db=test" --data-urlencode {shlex.quote("q="+ qv)}'
+    out = one.succeed(cmd)
+
+    assert "2015-06-11T20:46:02Z" in out
+    assert "0.64" in out
+  '';
+})
diff --git a/nixpkgs/nixos/tests/influxdb2.nix b/nixpkgs/nixos/tests/influxdb2.nix
new file mode 100644
index 000000000000..1631ac1d9408
--- /dev/null
+++ b/nixpkgs/nixos/tests/influxdb2.nix
@@ -0,0 +1,225 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "influxdb2";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes.machine = { lib, ... }: {
+    environment.systemPackages = [ pkgs.influxdb2-cli ];
+    # Make sure that the service is restarted immediately if tokens need to be rewritten
+    # without relying on any Restart=on-failure behavior
+    systemd.services.influxdb2.serviceConfig.RestartSec = 6000;
+    services.influxdb2.enable = true;
+    services.influxdb2.provision = {
+      enable = true;
+      initialSetup = {
+        organization = "default";
+        bucket = "default";
+        passwordFile = pkgs.writeText "admin-pw" "ExAmPl3PA55W0rD";
+        tokenFile = pkgs.writeText "admin-token" "verysecureadmintoken";
+      };
+      organizations.someorg = {
+        buckets.somebucket = {};
+        auths.sometoken = {
+          description = "some auth token";
+          readBuckets = ["somebucket"];
+          writeBuckets = ["somebucket"];
+        };
+      };
+      users.someuser.passwordFile = pkgs.writeText "tmp-pw" "abcgoiuhaoga";
+    };
+
+    specialisation.withModifications.configuration = { ... }: {
+      services.influxdb2.provision = {
+        organizations.someorg.buckets.somebucket.present = false;
+        organizations.someorg.auths.sometoken.present = false;
+        users.someuser.present = false;
+
+        organizations.myorg = {
+          description = "Myorg description";
+          buckets.mybucket = {
+            description = "Mybucket description";
+          };
+          auths.mytoken = {
+            operator = true;
+            description = "operator token";
+            tokenFile = pkgs.writeText "tmp-tok" "someusertoken";
+          };
+        };
+        users.myuser.passwordFile = pkgs.writeText "tmp-pw" "abcgoiuhaoga";
+      };
+    };
+
+    specialisation.withParentDelete.configuration = { ... }: {
+      services.influxdb2.provision = {
+        organizations.someorg.present = false;
+        # Deleting the parent implies:
+        #organizations.someorg.buckets.somebucket.present = false;
+        #organizations.someorg.auths.sometoken.present = false;
+      };
+    };
+
+    specialisation.withNewTokens.configuration = { ... }: {
+      services.influxdb2.provision = {
+        organizations.default = {
+          auths.operator = {
+            operator = true;
+            description = "new optoken";
+            tokenFile = pkgs.writeText "tmp-tok" "newoptoken";
+          };
+          auths.allaccess = {
+            operator = true;
+            description = "new allaccess";
+            tokenFile = pkgs.writeText "tmp-tok" "newallaccess";
+          };
+          auths.specifics = {
+            description = "new specifics";
+            readPermissions = ["users" "tasks"];
+            writePermissions = ["tasks"];
+            tokenFile = pkgs.writeText "tmp-tok" "newspecificstoken";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+      tokenArg = "--token verysecureadmintoken";
+    in ''
+      def assert_contains(haystack, needle):
+          if needle not in haystack:
+              print("The haystack that will cause the following exception is:")
+              print("---")
+              print(haystack)
+              print("---")
+              raise Exception(f"Expected string '{needle}' was not found")
+
+      def assert_lacks(haystack, needle):
+          if needle in haystack:
+              print("The haystack that will cause the following exception is:")
+              print("---")
+              print(haystack, end="")
+              print("---")
+              raise Exception(f"Unexpected string '{needle}' was found")
+
+      machine.wait_for_unit("influxdb2.service")
+
+      machine.fail("curl --fail -X POST 'http://localhost:8086/api/v2/signin' -u admin:wrongpassword")
+      machine.succeed("curl --fail -X POST 'http://localhost:8086/api/v2/signin' -u admin:ExAmPl3PA55W0rD")
+
+      out = machine.succeed("influx org list ${tokenArg}")
+      assert_contains(out, "default")
+      assert_lacks(out, "myorg")
+      assert_contains(out, "someorg")
+
+      out = machine.succeed("influx bucket list ${tokenArg} --org default")
+      assert_contains(out, "default")
+
+      machine.fail("influx bucket list ${tokenArg} --org myorg")
+
+      out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
+      assert_contains(out, "somebucket")
+
+      out = machine.succeed("influx user list ${tokenArg}")
+      assert_contains(out, "admin")
+      assert_lacks(out, "myuser")
+      assert_contains(out, "someuser")
+
+      out = machine.succeed("influx auth list ${tokenArg}")
+      assert_lacks(out, "operator token")
+      assert_contains(out, "some auth token")
+
+      with subtest("withModifications"):
+        machine.succeed('${specialisations}/withModifications/bin/switch-to-configuration test')
+        machine.wait_for_unit("influxdb2.service")
+
+        out = machine.succeed("influx org list ${tokenArg}")
+        assert_contains(out, "default")
+        assert_contains(out, "myorg")
+        assert_contains(out, "someorg")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
+        assert_contains(out, "mybucket")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
+        assert_lacks(out, "somebucket")
+
+        out = machine.succeed("influx user list ${tokenArg}")
+        assert_contains(out, "admin")
+        assert_contains(out, "myuser")
+        assert_lacks(out, "someuser")
+
+        out = machine.succeed("influx auth list ${tokenArg}")
+        assert_contains(out, "operator token")
+        assert_lacks(out, "some auth token")
+
+        # Make sure the user token is also usable
+        machine.succeed("influx auth list --token someusertoken")
+
+      with subtest("keepsUnrelated"):
+        machine.succeed('${nodes.machine.system.build.toplevel}/bin/switch-to-configuration test')
+        machine.wait_for_unit("influxdb2.service")
+
+        out = machine.succeed("influx org list ${tokenArg}")
+        assert_contains(out, "default")
+        assert_contains(out, "myorg")
+        assert_contains(out, "someorg")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org default")
+        assert_contains(out, "default")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
+        assert_contains(out, "mybucket")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
+        assert_contains(out, "somebucket")
+
+        out = machine.succeed("influx user list ${tokenArg}")
+        assert_contains(out, "admin")
+        assert_contains(out, "myuser")
+        assert_contains(out, "someuser")
+
+        out = machine.succeed("influx auth list ${tokenArg}")
+        assert_contains(out, "operator token")
+        assert_contains(out, "some auth token")
+
+      with subtest("withParentDelete"):
+        machine.succeed('${specialisations}/withParentDelete/bin/switch-to-configuration test')
+        machine.wait_for_unit("influxdb2.service")
+
+        out = machine.succeed("influx org list ${tokenArg}")
+        assert_contains(out, "default")
+        assert_contains(out, "myorg")
+        assert_lacks(out, "someorg")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org default")
+        assert_contains(out, "default")
+
+        out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
+        assert_contains(out, "mybucket")
+
+        machine.fail("influx bucket list ${tokenArg} --org someorg")
+
+        out = machine.succeed("influx user list ${tokenArg}")
+        assert_contains(out, "admin")
+        assert_contains(out, "myuser")
+        assert_contains(out, "someuser")
+
+        out = machine.succeed("influx auth list ${tokenArg}")
+        assert_contains(out, "operator token")
+        assert_lacks(out, "some auth token")
+
+      with subtest("withNewTokens"):
+        machine.succeed('${specialisations}/withNewTokens/bin/switch-to-configuration test')
+        machine.wait_for_unit("influxdb2.service")
+
+        out = machine.succeed("influx auth list ${tokenArg}")
+        assert_contains(out, "operator token")
+        assert_contains(out, "some auth token")
+        assert_contains(out, "new optoken")
+        assert_contains(out, "new allaccess")
+        assert_contains(out, "new specifics")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/initrd-luks-empty-passphrase.nix b/nixpkgs/nixos/tests/initrd-luks-empty-passphrase.nix
new file mode 100644
index 000000000000..a846c120415d
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-luks-empty-passphrase.nix
@@ -0,0 +1,105 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. {inherit system config; }
+, systemdStage1 ? false }:
+import ./make-test-python.nix ({ lib, pkgs, ... }: let
+
+  keyfile = pkgs.writeText "luks-keyfile" ''
+    MIGHAoGBAJ4rGTSo/ldyjQypd0kuS7k2OSsmQYzMH6TNj3nQ/vIUjDn7fqa3slt2
+    gV6EK3TmTbGc4tzC1v4SWx2m+2Bjdtn4Fs4wiBwn1lbRdC6i5ZYCqasTWIntWn+6
+    FllUkMD5oqjOR/YcboxG8Z3B5sJuvTP9llsF+gnuveWih9dpbBr7AgEC
+  '';
+
+in {
+  name = "initrd-luks-empty-passphrase";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = lib.optionals (!systemdStage1) [ ./common/auto-format-root-device.nix ];
+
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = true;
+      useEFIBoot = true;
+      # This requires to have access
+      # to a host Nix store as
+      # the new root device is /dev/vdb
+      # an empty 512MiB drive, containing no Nix store.
+      mountHostNixStore = true;
+      fileSystems."/".autoFormat = lib.mkIf systemdStage1 true;
+    };
+
+    boot.loader.systemd-boot.enable = true;
+    boot.initrd.systemd = lib.mkIf systemdStage1 {
+      enable = true;
+      emergencyAccess = true;
+    };
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+
+    specialisation.boot-luks-wrong-keyfile.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          keyFile = "/etc/cryptroot.key";
+          tryEmptyPassphrase = true;
+          fallbackToPassword = !systemdStage1;
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      boot.initrd.secrets."/etc/cryptroot.key" = keyfile;
+    };
+
+    specialisation.boot-luks-missing-keyfile.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          keyFile = "/etc/cryptroot.key";
+          tryEmptyPassphrase = true;
+          fallbackToPassword = !systemdStage1;
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+    };
+  };
+
+  testScript = ''
+    # Encrypt key with empty key so boot should try keyfile and then fallback to empty passphrase
+
+
+    def grub_select_boot_luks_wrong_key_file():
+        """
+        Selects "boot-luks" from the GRUB menu
+        to trigger a login request.
+        """
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey ret")
+
+    def grub_select_boot_luks_missing_key_file():
+        """
+        Selects "boot-luks" from the GRUB menu
+        to trigger a login request.
+        """
+        machine.send_monitor_command("sendkey down")
+        machine.send_monitor_command("sendkey ret")
+
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo "" | cryptsetup luksFormat /dev/vdb --batch-mode")
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-wrong-keyfile.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Check if rootfs is on /dev/mapper/cryptroot
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+
+    # Choose boot-luks-missing-keyfile specialisation
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-missing-keyfile.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Check if rootfs is on /dev/mapper/cryptroot
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/initrd-network-openvpn/default.nix b/nixpkgs/nixos/tests/initrd-network-openvpn/default.nix
new file mode 100644
index 000000000000..69db7dd1037f
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-openvpn/default.nix
@@ -0,0 +1,165 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, systemdStage1 ? false
+}:
+
+import ../make-test-python.nix ({ lib, ...}:
+
+{
+  name = "initrd-network-openvpn";
+
+  nodes =
+    let
+
+      # Inlining of the shared secret for the
+      # OpenVPN server and client
+      secretblock = ''
+        secret [inline]
+        <secret>
+        ${lib.readFile ./shared.key}
+        </secret>
+        '';
+
+    in
+    {
+
+      # Minimal test case to check a successful boot, even with invalid config
+      minimalboot =
+        { ... }:
+        {
+          boot.initrd.systemd.enable = systemdStage1;
+          boot.initrd.network = {
+            enable = true;
+            openvpn = {
+              enable = true;
+              configuration = builtins.toFile "initrd.ovpn" "";
+            };
+          };
+        };
+
+      # initrd VPN client
+      ovpnclient =
+        { ... }:
+        {
+          virtualisation.useBootLoader = true;
+          virtualisation.vlans = [ 1 ];
+
+          boot.initrd = {
+            systemd.enable = systemdStage1;
+            systemd.extraBin.nc = "${pkgs.busybox}/bin/nc";
+            systemd.services.nc = {
+              requiredBy = ["initrd.target"];
+              after = ["network.target"];
+              serviceConfig = {
+                ExecStart = "/bin/nc -p 1234 -lke /bin/echo TESTVALUE";
+                Type = "oneshot";
+              };
+            };
+
+            # This command does not fork to keep the VM in the state where
+            # only the initramfs is loaded
+            preLVMCommands = lib.mkIf (!systemdStage1)
+              ''
+                /bin/nc -p 1234 -lke /bin/echo TESTVALUE
+              '';
+
+            network = {
+              enable = true;
+
+              # Work around udhcpc only getting a lease on eth0
+              postCommands = lib.mkIf (!systemdStage1)
+                ''
+                  /bin/ip addr add 192.168.1.2/24 dev eth1
+                '';
+
+              # Example configuration for OpenVPN
+              # This is the main reason for this test
+              openvpn = {
+                enable = true;
+                configuration = "${./initrd.ovpn}";
+              };
+            };
+          };
+        };
+
+      # VPN server and gateway for ovpnclient between vlan 1 and 2
+      ovpnserver =
+        { ... }:
+        {
+          virtualisation.vlans = [ 1 2 ];
+
+          # Enable NAT and forward port 12345 to port 1234
+          networking.nat = {
+            enable = true;
+            internalInterfaces = [ "tun0" ];
+            externalInterface = "eth2";
+            forwardPorts = [ { destination = "10.8.0.2:1234";
+                               sourcePort = 12345; } ];
+          };
+
+          # Trust tun0 and allow the VPN Server to be reached
+          networking.firewall = {
+            trustedInterfaces = [ "tun0" ];
+            allowedUDPPorts = [ 1194 ];
+          };
+
+          # Minimal OpenVPN server configuration
+          services.openvpn.servers.testserver =
+          {
+            config = ''
+              dev tun0
+              ifconfig 10.8.0.1 10.8.0.2
+              cipher AES-256-CBC
+              ${secretblock}
+            '';
+          };
+        };
+
+      # Client that resides in the "external" VLAN
+      testclient =
+        { ... }:
+        {
+          virtualisation.vlans = [ 2 ];
+        };
+  };
+
+
+  testScript =
+    ''
+      # Minimal test case, checks whether enabling (with invalid config) harms
+      # the boot process
+      with subtest("Check for successful boot with broken openvpn config"):
+          minimalboot.start()
+          # If we get to multi-user.target, we booted successfully
+          minimalboot.wait_for_unit("multi-user.target")
+          minimalboot.shutdown()
+
+      # Elaborated test case where the ovpnclient (where this module is used)
+      # can be reached by testclient only over ovpnserver.
+      # This is an indirect test for success.
+      with subtest("Check for connection from initrd VPN client, config as file"):
+          ovpnserver.start()
+          testclient.start()
+          ovpnclient.start()
+
+          # Wait until the OpenVPN Server is available
+          ovpnserver.wait_for_unit("openvpn-testserver.service")
+          ovpnserver.succeed("ping -c 1 10.8.0.1")
+
+          # Wait for the client to connect
+          ovpnserver.wait_until_succeeds("ping -c 1 10.8.0.2")
+
+          # Wait until the testclient has network
+          testclient.wait_for_unit("network.target")
+
+          # Check that ovpnclient is reachable over vlan 1
+          ovpnserver.succeed("nc -w 2 192.168.1.2 1234 | grep -q TESTVALUE")
+
+          # Check that ovpnclient is reachable over tun0
+          ovpnserver.succeed("nc -w 2 10.8.0.2 1234 | grep -q TESTVALUE")
+
+          # Check that ovpnclient is reachable from testclient over the gateway
+          testclient.succeed("nc -w 2 192.168.2.3 12345 | grep -q TESTVALUE")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn b/nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn
new file mode 100644
index 000000000000..3ada4130e868
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-openvpn/initrd.ovpn
@@ -0,0 +1,30 @@
+remote 192.168.1.3
+dev tun
+ifconfig 10.8.0.2 10.8.0.1
+# Only force VLAN 2 through the VPN
+route 192.168.2.0 255.255.255.0 10.8.0.1
+cipher AES-256-CBC
+secret [inline]
+<secret>
+#
+# 2048 bit OpenVPN static key
+#
+-----BEGIN OpenVPN Static key V1-----
+553aabe853acdfe51cd6fcfea93dcbb0
+c8797deadd1187606b1ea8f2315eb5e6
+67c0d7e830f50df45686063b189d6c6b
+aab8bb3430cc78f7bb1f78628d5c3742
+0cef4f53a5acab2894905f4499f95d8e
+e69b7b6748b17016f89e19e91481a9fd
+bf8c10651f41a1d4fdf5f438925a6733
+13cec8f04701eb47b8f7ffc48bc3d7af
+65f07bce766015b87c3db4d668c655ff
+be5a69522a8e60ccb217f8521681b45d
+27c0b70bdfbfbb426c7646d80adf7482
+3ddac58b25cb1c1bb100de974478b4c6
+8b45a94261a2405e99810cb2b3abd49f
+21b3198ada87ff3c4e656a008e540a8d
+e7811584363597599cce2040a68ac00e
+f2125540e0f7f4adc37cb3f0d922eeb7
+-----END OpenVPN Static key V1-----
+</secret>
diff --git a/nixpkgs/nixos/tests/initrd-network-openvpn/shared.key b/nixpkgs/nixos/tests/initrd-network-openvpn/shared.key
new file mode 100644
index 000000000000..248a91a3e3d5
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-openvpn/shared.key
@@ -0,0 +1,21 @@
+#
+# 2048 bit OpenVPN static key
+#
+-----BEGIN OpenVPN Static key V1-----
+553aabe853acdfe51cd6fcfea93dcbb0
+c8797deadd1187606b1ea8f2315eb5e6
+67c0d7e830f50df45686063b189d6c6b
+aab8bb3430cc78f7bb1f78628d5c3742
+0cef4f53a5acab2894905f4499f95d8e
+e69b7b6748b17016f89e19e91481a9fd
+bf8c10651f41a1d4fdf5f438925a6733
+13cec8f04701eb47b8f7ffc48bc3d7af
+65f07bce766015b87c3db4d668c655ff
+be5a69522a8e60ccb217f8521681b45d
+27c0b70bdfbfbb426c7646d80adf7482
+3ddac58b25cb1c1bb100de974478b4c6
+8b45a94261a2405e99810cb2b3abd49f
+21b3198ada87ff3c4e656a008e540a8d
+e7811584363597599cce2040a68ac00e
+f2125540e0f7f4adc37cb3f0d922eeb7
+-----END OpenVPN Static key V1-----
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/default.nix b/nixpkgs/nixos/tests/initrd-network-ssh/default.nix
new file mode 100644
index 000000000000..17b6c21ff1e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/default.nix
@@ -0,0 +1,73 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "initrd-network-ssh";
+  meta.maintainers = with lib.maintainers; [ willibutz emily ];
+
+  nodes = {
+    server =
+      { config, ... }:
+      {
+        boot.kernelParams = [
+          "ip=${config.networking.primaryIPAddress}:::255.255.255.0::eth1:none"
+        ];
+        boot.initrd.network = {
+          enable = true;
+          ssh = {
+            enable = true;
+            authorizedKeys = [ (lib.readFile ./id_ed25519.pub) ];
+            port = 22;
+            hostKeys = [ ./ssh_host_ed25519_key ];
+          };
+        };
+        boot.initrd.preLVMCommands = ''
+          while true; do
+            if [ -f fnord ]; then
+              poweroff
+            fi
+            sleep 1
+          done
+        '';
+      };
+
+    client =
+      { config, ... }:
+      {
+        environment.etc = {
+          knownHosts = {
+            text = lib.concatStrings [
+              "server,"
+              "${toString (lib.head (lib.splitString " " (
+                toString (lib.elemAt (lib.splitString "\n" config.networking.extraHosts) 2)
+              )))} "
+              "${lib.readFile ./ssh_host_ed25519_key.pub}"
+            ];
+          };
+          sshKey = {
+            source = ./id_ed25519;
+            mode = "0600";
+          };
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    client.wait_for_unit("network.target")
+
+
+    def ssh_is_up(_) -> bool:
+        status, _ = client.execute("nc -z server 22")
+        return status == 0
+
+
+    with client.nested("waiting for SSH server to come up"):
+        retry(ssh_is_up)
+
+
+    client.succeed(
+        "ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'touch /fnord'"
+    )
+    client.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/generate-keys.nix b/nixpkgs/nixos/tests/initrd-network-ssh/generate-keys.nix
new file mode 100644
index 000000000000..3d7978890ab0
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/generate-keys.nix
@@ -0,0 +1,10 @@
+with import ../../.. {};
+
+runCommand "gen-keys" {
+    buildInputs = [ openssh ];
+  }
+  ''
+    mkdir $out
+    ssh-keygen -q -t ed25519 -N "" -f $out/ssh_host_ed25519_key
+    ssh-keygen -q -t ed25519 -N "" -f $out/id_ed25519
+  ''
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/id_ed25519 b/nixpkgs/nixos/tests/initrd-network-ssh/id_ed25519
new file mode 100644
index 000000000000..f914b3f712fc
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/id_ed25519
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACAVcX+32Yqig25RxRA8bel/f604wV0p/63um+Oku/3vfwAAAJi/AJZMvwCW
+TAAAAAtzc2gtZWQyNTUxOQAAACAVcX+32Yqig25RxRA8bel/f604wV0p/63um+Oku/3vfw
+AAAEAPLjQusjrB90Lk3996G3AbtTeK+XweNgxaegYnml/A/RVxf7fZiqKDblHFEDxt6X9/
+rTjBXSn/re6b46S7/e9/AAAAEG5peGJsZEBsb2NhbGhvc3QBAgMEBQ==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/id_ed25519.pub b/nixpkgs/nixos/tests/initrd-network-ssh/id_ed25519.pub
new file mode 100644
index 000000000000..40de4a8ac602
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/id_ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBVxf7fZiqKDblHFEDxt6X9/rTjBXSn/re6b46S7/e9/ nixbld@localhost
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key b/nixpkgs/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key
new file mode 100644
index 000000000000..f1e29459b7a3
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACDP9Mz6qlxdQqA4omrgbOlVsxSGONCJstjW9zqquajlIAAAAJg0WGFGNFhh
+RgAAAAtzc2gtZWQyNTUxOQAAACDP9Mz6qlxdQqA4omrgbOlVsxSGONCJstjW9zqquajlIA
+AAAEA0Hjs7LfFPdTf3ThGx6GNKvX0ItgzgXs91Z3oGIaF6S8/0zPqqXF1CoDiiauBs6VWz
+FIY40Imy2Nb3Oqq5qOUgAAAAEG5peGJsZEBsb2NhbGhvc3QBAgMEBQ==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key.pub b/nixpkgs/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key.pub
new file mode 100644
index 000000000000..3aa1587e1dce
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network-ssh/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM/0zPqqXF1CoDiiauBs6VWzFIY40Imy2Nb3Oqq5qOUg nixbld@localhost
diff --git a/nixpkgs/nixos/tests/initrd-network.nix b/nixpkgs/nixos/tests/initrd-network.nix
new file mode 100644
index 000000000000..f2483b7393de
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-network.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "initrd-network";
+
+  meta.maintainers = [ pkgs.lib.maintainers.eelco ];
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    boot.initrd.network.enable = true;
+    boot.initrd.network.postCommands =
+      ''
+        ip addr show
+        ip route show
+        ip addr | grep 10.0.2.15 || exit 1
+        ping -c1 10.0.2.2 || exit 1
+      '';
+    # Check if cleanup was done correctly
+    boot.initrd.postMountCommands = lib.mkAfter
+      ''
+        ip addr show
+        ip route show
+        ip addr | grep 10.0.2.15 && exit 1
+        ping -c1 10.0.2.2 && exit 1
+      '';
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("ip addr show >&2")
+      machine.succeed("ip route show >&2")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/initrd-secrets-changing.nix b/nixpkgs/nixos/tests/initrd-secrets-changing.nix
new file mode 100644
index 000000000000..d6f9ef9ced83
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-secrets-changing.nix
@@ -0,0 +1,57 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+, testing ? import ../lib/testing-python.nix { inherit system pkgs; }
+}:
+
+let
+  secret1InStore = pkgs.writeText "topsecret" "iamasecret1";
+  secret2InStore = pkgs.writeText "topsecret" "iamasecret2";
+in
+
+testing.makeTest {
+  name = "initrd-secrets-changing";
+
+  nodes.machine = { ... }: {
+    virtualisation.useBootLoader = true;
+
+    boot.loader.grub.device = "/dev/vda";
+
+    boot.initrd.secrets = {
+      "/test" = secret1InStore;
+      "/run/keys/test" = secret1InStore;
+    };
+    boot.initrd.postMountCommands = "cp /test /mnt-root/secret-from-initramfs";
+
+    specialisation.secrets2System.configuration = {
+      boot.initrd.secrets = lib.mkForce {
+        "/test" = secret2InStore;
+        "/run/keys/test" = secret2InStore;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("multi-user.target")
+    print(machine.succeed("cat /run/keys/test"))
+    machine.succeed(
+        "cmp ${secret1InStore} /secret-from-initramfs",
+        "cmp ${secret1InStore} /run/keys/test",
+    )
+    # Select the second boot entry corresponding to the specialisation secrets2System.
+    machine.succeed("grub-reboot 1")
+    machine.shutdown()
+
+    with subtest("Check that the specialisation's secrets are distinct despite identical kernels"):
+        machine.wait_for_unit("multi-user.target")
+        print(machine.succeed("cat /run/keys/test"))
+        machine.succeed(
+            "cmp ${secret2InStore} /secret-from-initramfs",
+            "cmp ${secret2InStore} /run/keys/test",
+        )
+        machine.shutdown()
+  '';
+}
diff --git a/nixpkgs/nixos/tests/initrd-secrets.nix b/nixpkgs/nixos/tests/initrd-secrets.nix
new file mode 100644
index 000000000000..0f3f83b0904e
--- /dev/null
+++ b/nixpkgs/nixos/tests/initrd-secrets.nix
@@ -0,0 +1,41 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+, testing ? import ../lib/testing-python.nix { inherit system pkgs; }
+}:
+let
+  secretInStore = pkgs.writeText "topsecret" "iamasecret";
+  testWithCompressor = compressor: testing.makeTest {
+    name = "initrd-secrets-${compressor}";
+
+    meta.maintainers = [ lib.maintainers.lheckemann ];
+
+    nodes.machine = { ... }: {
+      virtualisation.useBootLoader = true;
+      boot.initrd.secrets = {
+        "/test" = secretInStore;
+
+        # This should *not* need to be copied in postMountCommands
+        "/run/keys/test" = secretInStore;
+      };
+      boot.initrd.postMountCommands = ''
+        cp /test /mnt-root/secret-from-initramfs
+      '';
+      boot.initrd.compressor = compressor;
+      # zstd compression is only supported from 5.9 onwards. Remove when 5.10 becomes default.
+      boot.kernelPackages = pkgs.linuxPackages_latest;
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed(
+          "cmp ${secretInStore} /secret-from-initramfs",
+          "cmp ${secretInStore} /run/keys/test",
+      )
+    '';
+  };
+in lib.flip lib.genAttrs testWithCompressor [
+  "cat" "gzip" "bzip2" "xz" "lzma" "lzop" "pigz" "pixz" "zstd"
+]
diff --git a/nixpkgs/nixos/tests/input-remapper.nix b/nixpkgs/nixos/tests/input-remapper.nix
new file mode 100644
index 000000000000..2ef55a01b290
--- /dev/null
+++ b/nixpkgs/nixos/tests/input-remapper.nix
@@ -0,0 +1,53 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  {
+    name = "input-remapper";
+    meta = {
+      maintainers = with pkgs.lib.maintainers; [ LunNova ];
+    };
+
+    nodes.machine = { config, ... }:
+      let user = config.users.users.sybil; in
+      {
+        imports = [
+          ./common/user-account.nix
+          ./common/x11.nix
+        ];
+
+        services.xserver.enable = true;
+        services.input-remapper.enable = true;
+        users.users.sybil = { isNormalUser = true; group = "wheel"; };
+        test-support.displayManager.auto.user = user.name;
+        # workaround for pkexec not working in the test environment
+        # Error creating textual authentication agent:
+        #   Error opening current controlling terminal for the process (`/dev/tty'):
+        #   No such device or address
+        # passwordless pkexec with polkit module also doesn't work
+        # to allow the program to run, we replace pkexec with sudo
+        # and turn on passwordless sudo
+        # this is not correct in general but good enough for this test
+        security.sudo = { enable = true; wheelNeedsPassword = false; };
+        security.wrappers.pkexec = pkgs.lib.mkForce
+          {
+            setuid = true;
+            owner = "root";
+            group = "root";
+            source = "${pkgs.sudo}/bin/sudo";
+          };
+      };
+
+    enableOCR = true;
+
+    testScript = { nodes, ... }: ''
+      start_all()
+      machine.wait_for_x()
+
+      machine.succeed("systemctl status input-remapper.service")
+      machine.execute("su - sybil -c input-remapper-gtk >&2 &")
+
+      machine.wait_for_text("Input Remapper")
+      machine.wait_for_text("Device")
+      machine.wait_for_text("Presets")
+      machine.wait_for_text("Editor")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/inspircd.nix b/nixpkgs/nixos/tests/inspircd.nix
new file mode 100644
index 000000000000..f4d82054011c
--- /dev/null
+++ b/nixpkgs/nixos/tests/inspircd.nix
@@ -0,0 +1,93 @@
+let
+  clients = [
+    "ircclient1"
+    "ircclient2"
+  ];
+  server = "inspircd";
+  ircPort = 6667;
+  channel = "nixos-cat";
+  iiDir = "/tmp/irc";
+in
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "inspircd";
+  nodes = {
+    "${server}" = {
+      networking.firewall.allowedTCPPorts = [ ircPort ];
+      services.inspircd = {
+        enable = true;
+        package = pkgs.inspircdMinimal;
+        config = ''
+          <bind address="" port="${toString ircPort}" type="clients">
+          <connect name="main" allow="*" pingfreq="15">
+        '';
+      };
+    };
+  } // lib.listToAttrs (builtins.map (client: lib.nameValuePair client {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    systemd.services.ii = {
+      requires = [ "network.target" ];
+      wantedBy = [ "default.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        ExecPreStartPre = "mkdir -p ${iiDir}";
+        ExecStart = ''
+          ${lib.getBin pkgs.ii}/bin/ii -n ${client} -s ${server} -i ${iiDir}
+        '';
+        User = "alice";
+      };
+    };
+  }) clients);
+
+  testScript =
+    let
+      msg = client: "Hello, my name is ${client}";
+      clientScript = client: [
+        ''
+          ${client}.wait_for_unit("network.target")
+          ${client}.systemctl("start ii")
+          ${client}.wait_for_unit("ii")
+          ${client}.wait_for_file("${iiDir}/${server}/out")
+        ''
+        # wait until first PING from server arrives before joining,
+        # so we don't try it too early
+        ''
+          ${client}.wait_until_succeeds("grep 'PING' ${iiDir}/${server}/out")
+        ''
+        # join ${channel}
+        ''
+          ${client}.succeed("echo '/j #${channel}' > ${iiDir}/${server}/in")
+          ${client}.wait_for_file("${iiDir}/${server}/#${channel}/in")
+        ''
+        # send a greeting
+        ''
+          ${client}.succeed(
+              "echo '${msg client}' > ${iiDir}/${server}/#${channel}/in"
+          )
+        ''
+        # check that all greetings arrived on all clients
+      ] ++ builtins.map (other: ''
+        ${client}.succeed(
+            "grep '${msg other}$' ${iiDir}/${server}/#${channel}/out"
+        )
+      '') clients;
+
+      # foldl', but requires a non-empty list instead of a start value
+      reduce = f: list:
+        builtins.foldl' f (builtins.head list) (builtins.tail list);
+    in ''
+      start_all()
+      ${server}.wait_for_open_port(${toString ircPort})
+
+      # run clientScript for all clients so that every list
+      # entry is executed by every client before advancing
+      # to the next one.
+    '' + lib.concatStrings
+      (reduce
+        (lib.zipListsWith (cs: c: cs + c))
+        (builtins.map clientScript clients));
+})
diff --git a/nixpkgs/nixos/tests/installed-tests/appstream-qt.nix b/nixpkgs/nixos/tests/installed-tests/appstream-qt.nix
new file mode 100644
index 000000000000..d08187bfe466
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/appstream-qt.nix
@@ -0,0 +1,9 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.libsForQt5.appstream-qt;
+
+  testConfig = {
+    appstream.enable = true;
+  };
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/appstream.nix b/nixpkgs/nixos/tests/installed-tests/appstream.nix
new file mode 100644
index 000000000000..f71a095d4452
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/appstream.nix
@@ -0,0 +1,9 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.appstream;
+
+  testConfig = {
+    appstream.enable = true;
+  };
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/colord.nix b/nixpkgs/nixos/tests/installed-tests/colord.nix
new file mode 100644
index 000000000000..77e6b917fe68
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/colord.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.colord;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/default.nix b/nixpkgs/nixos/tests/installed-tests/default.nix
new file mode 100644
index 000000000000..e87edb2007e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/default.nix
@@ -0,0 +1,112 @@
+# NixOS tests for gnome-desktop-testing-runner using software
+# See https://wiki.gnome.org/Initiatives/GnomeGoals/InstalledTests
+
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; }
+}:
+
+with import ../../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+
+  callInstalledTest = pkgs.newScope { inherit makeInstalledTest; };
+
+  makeInstalledTest =
+    { # Package to test. Needs to have an installedTests output
+      tested
+
+      # Config to inject into machine
+    , testConfig ? {}
+
+      # Test script snippet to inject before gnome-desktop-testing-runner begins.
+      # This is useful for extra setup the environment may need before the runner begins.
+    , preTestScript ? ""
+
+      # Does test need X11?
+    , withX11 ? false
+
+      # Extra flags to pass to gnome-desktop-testing-runner.
+    , testRunnerFlags ? []
+
+      # Extra attributes to pass to makeTest.
+      # They will be recursively merged into the attrset created by this function.
+    , ...
+    }@args:
+    makeTest
+      (recursiveUpdate
+        rec {
+          name = tested.name;
+
+          meta = {
+            maintainers = tested.meta.maintainers or [];
+          };
+
+          nodes.machine = { ... }: {
+            imports = [
+              testConfig
+            ] ++ optional withX11 ../common/x11.nix;
+
+            environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
+
+            # The installed tests need to be added to the test VM’s closure.
+            # Otherwise, their dependencies might not actually be registered
+            # as valid paths in the VM’s Nix store database,
+            # and `nix-store --query` commands run as part of the tests
+            # (for example when building Flatpak runtimes) will fail.
+            environment.variables.TESTED_PACKAGE_INSTALLED_TESTS = "${tested.installedTests}/share";
+          };
+
+          testScript =
+            optionalString withX11 ''
+              machine.wait_for_x()
+            '' +
+            optionalString (preTestScript != "") ''
+              ${preTestScript}
+            '' +
+            ''
+              machine.succeed(
+                  "gnome-desktop-testing-runner ${escapeShellArgs testRunnerFlags} -d '${tested.installedTests}/share'"
+              )
+            '';
+        }
+
+        (removeAttrs args [
+          "tested"
+          "testConfig"
+          "preTestScript"
+          "withX11"
+          "testRunnerFlags"
+        ])
+      );
+
+in
+
+{
+  appstream = callInstalledTest ./appstream.nix {};
+  appstream-qt = callInstalledTest ./appstream-qt.nix {};
+  colord = callInstalledTest ./colord.nix {};
+  flatpak = callInstalledTest ./flatpak.nix {};
+  flatpak-builder = callInstalledTest ./flatpak-builder.nix {};
+  fwupd = callInstalledTest ./fwupd.nix {};
+  gcab = callInstalledTest ./gcab.nix {};
+  gdk-pixbuf = callInstalledTest ./gdk-pixbuf.nix {};
+  geocode-glib = callInstalledTest ./geocode-glib.nix {};
+  gjs = callInstalledTest ./gjs.nix {};
+  glib-networking = callInstalledTest ./glib-networking.nix {};
+  gnome-photos = callInstalledTest ./gnome-photos.nix {};
+  graphene = callInstalledTest ./graphene.nix {};
+  gsconnect = callInstalledTest ./gsconnect.nix {};
+  json-glib = callInstalledTest ./json-glib.nix {};
+  ibus = callInstalledTest ./ibus.nix {};
+  libgdata = callInstalledTest ./libgdata.nix {};
+  glib-testing = callInstalledTest ./glib-testing.nix {};
+  libjcat = callInstalledTest ./libjcat.nix {};
+  libxmlb = callInstalledTest ./libxmlb.nix {};
+  malcontent = callInstalledTest ./malcontent.nix {};
+  ostree = callInstalledTest ./ostree.nix {};
+  pipewire = callInstalledTest ./pipewire.nix {};
+  upower = callInstalledTest ./upower.nix {};
+  xdg-desktop-portal = callInstalledTest ./xdg-desktop-portal.nix {};
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix b/nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix
new file mode 100644
index 000000000000..d5e04fcf975c
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/flatpak-builder.nix
@@ -0,0 +1,15 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.flatpak-builder;
+
+  testConfig = {
+    services.flatpak.enable = true;
+    xdg.portal.enable = true;
+    xdg.portal.extraPortals = with pkgs; [ xdg-desktop-portal-gtk ];
+    environment.systemPackages = with pkgs; [ flatpak-builder ] ++ flatpak-builder.installedTestsDependencies;
+    virtualisation.diskSize = 2048;
+  };
+
+  testRunnerFlags = [ "--timeout" "3600" ];
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/flatpak.nix b/nixpkgs/nixos/tests/installed-tests/flatpak.nix
new file mode 100644
index 000000000000..fa191202f52d
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/flatpak.nix
@@ -0,0 +1,18 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.flatpak;
+  withX11 = true;
+
+  testConfig = {
+    xdg.portal.enable = true;
+    xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+    xdg.portal.config.common.default = "gtk";
+    services.flatpak.enable = true;
+    environment.systemPackages = with pkgs; [ gnupg ostree python3 ];
+    virtualisation.memorySize = 2047;
+    virtualisation.diskSize = 3072;
+  };
+
+  testRunnerFlags = [ "--timeout" "3600" ];
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/fwupd.nix b/nixpkgs/nixos/tests/installed-tests/fwupd.nix
new file mode 100644
index 000000000000..fe4f443d7004
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/fwupd.nix
@@ -0,0 +1,12 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.fwupd;
+
+  testConfig = {
+    services.fwupd = {
+      enable = true;
+      daemonSettings.TestDevices = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/gcab.nix b/nixpkgs/nixos/tests/installed-tests/gcab.nix
new file mode 100644
index 000000000000..b24cc2e01267
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/gcab.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.gcab;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix b/nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix
new file mode 100644
index 000000000000..110efdbf710f
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/gdk-pixbuf.nix
@@ -0,0 +1,13 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.gdk-pixbuf;
+
+  testConfig = {
+    # Tests allocate a lot of memory trying to exploit a CVE
+    # but qemu-system-i386 has a 2047M memory limit
+    virtualisation.memorySize = if pkgs.stdenv.isi686 then 2047 else 4096;
+  };
+
+  testRunnerFlags = [ "--timeout" "1800" ];
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/geocode-glib.nix b/nixpkgs/nixos/tests/installed-tests/geocode-glib.nix
new file mode 100644
index 000000000000..fcb38c96ab0f
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/geocode-glib.nix
@@ -0,0 +1,13 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  testConfig = {
+    i18n.supportedLocales = [
+      "en_US.UTF-8/UTF-8"
+      # The tests require this locale available.
+      "en_GB.UTF-8/UTF-8"
+    ];
+  };
+
+  tested = pkgs.geocode-glib;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/gjs.nix b/nixpkgs/nixos/tests/installed-tests/gjs.nix
new file mode 100644
index 000000000000..d12487cba249
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/gjs.nix
@@ -0,0 +1,12 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.gjs;
+  withX11 = true;
+
+  testConfig = {
+    environment.systemPackages = [
+      pkgs.gjs
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/glib-networking.nix b/nixpkgs/nixos/tests/installed-tests/glib-networking.nix
new file mode 100644
index 000000000000..b58d4df21fca
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/glib-networking.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.glib-networking;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/glib-testing.nix b/nixpkgs/nixos/tests/installed-tests/glib-testing.nix
new file mode 100644
index 000000000000..7a06cf792bdd
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/glib-testing.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.glib-testing;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/gnome-photos.nix b/nixpkgs/nixos/tests/installed-tests/gnome-photos.nix
new file mode 100644
index 000000000000..bcb6479ee89c
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/gnome-photos.nix
@@ -0,0 +1,35 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.gnome-photos;
+
+  withX11 = true;
+
+  testConfig = {
+    programs.dconf.enable = true;
+    services.gnome.at-spi2-core.enable = true; # needed for dogtail
+    environment.systemPackages = with pkgs; [
+      # gsettings tool with access to gsettings-desktop-schemas
+      (stdenv.mkDerivation {
+        name = "desktop-gsettings";
+        dontUnpack = true;
+        nativeBuildInputs = [ glib wrapGAppsHook ];
+        buildInputs = [ gsettings-desktop-schemas ];
+        installPhase = ''
+          runHook preInstall
+          mkdir -p $out/bin
+          ln -s ${glib.bin}/bin/gsettings $out/bin/desktop-gsettings
+          runHook postInstall
+        '';
+      })
+    ];
+    services.dbus.packages = with pkgs; [ gnome-photos ];
+  };
+
+  preTestScript = ''
+    # dogtail needs accessibility enabled
+    machine.succeed(
+        "desktop-gsettings set org.gnome.desktop.interface toolkit-accessibility true 2>&1"
+    )
+  '';
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/graphene.nix b/nixpkgs/nixos/tests/installed-tests/graphene.nix
new file mode 100644
index 000000000000..e43339abd88c
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/graphene.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.graphene;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/gsconnect.nix b/nixpkgs/nixos/tests/installed-tests/gsconnect.nix
new file mode 100644
index 000000000000..ac39f7435786
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/gsconnect.nix
@@ -0,0 +1,7 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.gnomeExtensions.gsconnect;
+
+  withX11 = true;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/ibus.nix b/nixpkgs/nixos/tests/installed-tests/ibus.nix
new file mode 100644
index 000000000000..028c20c29f2d
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/ibus.nix
@@ -0,0 +1,17 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.ibus;
+
+  testConfig = {
+    i18n.supportedLocales = [ "all" ];
+    i18n.inputMethod.enabled = "ibus";
+    systemd.user.services.ibus-daemon = {
+      serviceConfig.ExecStart = "${pkgs.ibus}/bin/ibus-daemon --xim --verbose";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+    };
+  };
+
+  withX11 = true;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/json-glib.nix b/nixpkgs/nixos/tests/installed-tests/json-glib.nix
new file mode 100644
index 000000000000..3dfd3dd0b098
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/json-glib.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.json-glib;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/libgdata.nix b/nixpkgs/nixos/tests/installed-tests/libgdata.nix
new file mode 100644
index 000000000000..b0d39c042be4
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/libgdata.nix
@@ -0,0 +1,11 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.libgdata;
+
+  testConfig = {
+    # # GLib-GIO-DEBUG: _g_io_module_get_default: Found default implementation dummy (GDummyTlsBackend) for ‘gio-tls-backend’
+    # Bail out! libgdata:ERROR:../gdata/tests/common.c:134:gdata_test_init: assertion failed (child_error == NULL): TLS support is not available (g-tls-error-quark, 0)
+    services.gnome.glib-networking.enable = true;
+  };
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/libjcat.nix b/nixpkgs/nixos/tests/installed-tests/libjcat.nix
new file mode 100644
index 000000000000..41493a730890
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/libjcat.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.libjcat;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/libxmlb.nix b/nixpkgs/nixos/tests/installed-tests/libxmlb.nix
new file mode 100644
index 000000000000..af2bbe9c35e2
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/libxmlb.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.libxmlb;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/malcontent.nix b/nixpkgs/nixos/tests/installed-tests/malcontent.nix
new file mode 100644
index 000000000000..d4e214c41988
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/malcontent.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.malcontent;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/ostree.nix b/nixpkgs/nixos/tests/installed-tests/ostree.nix
new file mode 100644
index 000000000000..90e09ad4ddf4
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/ostree.nix
@@ -0,0 +1,12 @@
+{ pkgs, lib, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.ostree;
+
+  testConfig = {
+    environment.systemPackages = with pkgs; [
+      gnupg
+      ostree
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/pipewire.nix b/nixpkgs/nixos/tests/installed-tests/pipewire.nix
new file mode 100644
index 000000000000..6e69ada8612f
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/pipewire.nix
@@ -0,0 +1,5 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.pipewire;
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/upower.nix b/nixpkgs/nixos/tests/installed-tests/upower.nix
new file mode 100644
index 000000000000..a8e777a55527
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/upower.nix
@@ -0,0 +1,9 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.upower;
+
+  testConfig = {
+    services.upower.enable = true;
+  };
+}
diff --git a/nixpkgs/nixos/tests/installed-tests/xdg-desktop-portal.nix b/nixpkgs/nixos/tests/installed-tests/xdg-desktop-portal.nix
new file mode 100644
index 000000000000..90529d37ee0f
--- /dev/null
+++ b/nixpkgs/nixos/tests/installed-tests/xdg-desktop-portal.nix
@@ -0,0 +1,9 @@
+{ pkgs, makeInstalledTest, ... }:
+
+makeInstalledTest {
+  tested = pkgs.xdg-desktop-portal;
+
+  # Ton of breakage.
+  # https://github.com/flatpak/xdg-desktop-portal/pull/428
+  meta.broken = true;
+}
diff --git a/nixpkgs/nixos/tests/installer-systemd-stage-1.nix b/nixpkgs/nixos/tests/installer-systemd-stage-1.nix
new file mode 100644
index 000000000000..662017935412
--- /dev/null
+++ b/nixpkgs/nixos/tests/installer-systemd-stage-1.nix
@@ -0,0 +1,42 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+{
+  # Some of these tests don't work with systemd stage 1 yet. Uncomment
+  # them when fixed.
+  inherit (import ./installer.nix { inherit system config pkgs; systemdStage1 = true; })
+    # bcache
+    bcachefsSimple
+    bcachefsEncrypted
+    btrfsSimple
+    btrfsSubvolDefault
+    btrfsSubvolEscape
+    btrfsSubvols
+    encryptedFSWithKeyfile
+    # grub1
+    luksroot
+    luksroot-format1
+    luksroot-format2
+    # lvm
+    separateBoot
+    separateBootFat
+    separateBootZfs
+    simple
+    simpleLabels
+    simpleProvided
+    simpleSpecialised
+    simpleUefiGrub
+    simpleUefiGrubSpecialisation
+    simpleUefiSystemdBoot
+    stratisRoot
+    swraid
+    zfsroot
+    clevisLuks
+    clevisLuksFallback
+    clevisZfs
+    clevisZfsFallback
+    ;
+
+}
diff --git a/nixpkgs/nixos/tests/installer.nix b/nixpkgs/nixos/tests/installer.nix
new file mode 100644
index 000000000000..b6cb6a0c6d45
--- /dev/null
+++ b/nixpkgs/nixos/tests/installer.nix
@@ -0,0 +1,1429 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+  systemdStage1 ? false
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+
+  # The configuration to install.
+  makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi
+               , extraConfig, forceGrubReinstallCount ? 0, flake ? false
+               , clevisTest
+               }:
+    pkgs.writeText "configuration.nix" ''
+      { config, lib, pkgs, modulesPath, ... }:
+
+      { imports =
+          [ ./hardware-configuration.nix
+            ${if flake
+              then "" # Still included, but via installer/flake.nix
+              else "<nixpkgs/nixos/modules/testing/test-instrumentation.nix>"}
+          ];
+
+        networking.hostName = "thatworked";
+
+        documentation.enable = false;
+
+        # To ensure that we can rebuild the grub configuration on the nixos-rebuild
+        system.extraDependencies = with pkgs; [ stdenvNoCC ];
+
+        ${optionalString systemdStage1 "boot.initrd.systemd.enable = true;"}
+
+        ${optionalString (bootLoader == "grub") ''
+          boot.loader.grub.extraConfig = "serial; terminal_output serial";
+          ${if grubUseEfi then ''
+            boot.loader.grub.device = "nodev";
+            boot.loader.grub.efiSupport = true;
+            boot.loader.grub.efiInstallAsRemovable = true; # XXX: needed for OVMF?
+          '' else ''
+            boot.loader.grub.device = "${grubDevice}";
+            boot.loader.grub.fsIdentifier = "${grubIdentifier}";
+          ''}
+
+          boot.loader.grub.configurationLimit = 100 + ${toString forceGrubReinstallCount};
+        ''}
+
+        ${optionalString (bootLoader == "systemd-boot") ''
+          boot.loader.systemd-boot.enable = true;
+        ''}
+
+        boot.initrd.secrets."/etc/secret" = ./secret;
+
+        ${optionalString clevisTest ''
+          boot.kernelParams = [ "console=tty0" "ip=192.168.1.1:::255.255.255.0::eth1:none" ];
+          boot.initrd = {
+            availableKernelModules = [ "tpm_tis" ];
+            clevis = { enable = true; useTang = true; };
+            network.enable = true;
+          };
+          ''}
+
+        users.users.alice = {
+          isNormalUser = true;
+          home = "/home/alice";
+          description = "Alice Foobar";
+        };
+
+        hardware.enableAllFirmware = lib.mkForce false;
+
+        ${replaceStrings ["\n"] ["\n  "] extraConfig}
+      }
+    '';
+
+
+  # The test script boots a NixOS VM, installs NixOS on an empty hard
+  # disk, and then reboot from the hard disk.  It's parameterized with
+  # a test script fragment `createPartitions', which must create
+  # partitions and filesystems.
+  testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi, grubIdentifier
+                  , postInstallCommands, preBootCommands, postBootCommands, extraConfig
+                  , testSpecialisationConfig, testFlakeSwitch, clevisTest, clevisFallbackTest
+                  }:
+    let iface = "virtio";
+        isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
+        bios  = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd";
+    in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
+      machine.succeed("true")
+    '' else ''
+      import subprocess
+      tpm_folder = os.environ['NIX_BUILD_TOP']
+      def assemble_qemu_flags():
+          flags = "-cpu max"
+          ${if (system == "x86_64-linux" || system == "i686-linux")
+            then ''flags += " -m 1024"''
+            else ''flags += " -m 768 -enable-kvm -machine virt,gic-version=host"''
+          }
+          ${optionalString clevisTest ''flags += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"''}
+          ${optionalString clevisTest ''flags += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""''}
+          return flags
+
+
+      qemu_flags = {"qemuFlags": assemble_qemu_flags()}
+
+      import os
+
+      image_dir = machine.state_dir
+      disk_image = os.path.join(image_dir, "machine.qcow2")
+
+      hd_flags = {
+          "hdaInterface": "${iface}",
+          "hda": disk_image,
+      }
+      ${optionalString isEfi ''
+        hd_flags.update(
+            bios="${pkgs.OVMF.fd}/FV/${bios}"
+        )''
+      }
+      default_flags = {**hd_flags, **qemu_flags}
+
+
+      def create_machine_named(name):
+          return create_machine({**default_flags, "name": name})
+
+      class Tpm:
+            def __init__(self):
+                self.start()
+
+            def start(self):
+                self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm",
+                    "socket",
+                    "--tpmstate", f"dir={tpm_folder}/swtpm",
+                    "--ctrl", f"type=unixio,path={tpm_folder}/swtpm-sock",
+                    "--tpm2"
+                    ])
+
+                # Check whether starting swtpm failed
+                try:
+                    exit_code = self.proc.wait(timeout=0.2)
+                    if exit_code is not None and exit_code != 0:
+                        raise Exception("failed to start swtpm")
+                except subprocess.TimeoutExpired:
+                    pass
+
+            """Check whether the swtpm process exited due to an error"""
+            def check(self):
+                exit_code = self.proc.poll()
+                if exit_code is not None and exit_code != 0:
+                    raise Exception("swtpm process died")
+
+
+      os.mkdir(f"{tpm_folder}/swtpm")
+      tpm = Tpm()
+      tpm.check()
+
+      start_all()
+      ${optionalString clevisTest ''
+      tang.wait_for_unit("sockets.target")
+      tang.systemctl("start network-online.target")
+      tang.wait_for_unit("network-online.target")
+      machine.systemctl("start network-online.target")
+      machine.wait_for_unit("network-online.target")
+      ''}
+      machine.wait_for_unit("multi-user.target")
+
+
+      with subtest("Assert readiness of login prompt"):
+          machine.succeed("echo hello")
+
+      with subtest("Wait for hard disks to appear in /dev"):
+          machine.succeed("udevadm settle")
+
+      ${createPartitions}
+
+      with subtest("Create the NixOS configuration"):
+          machine.succeed("nixos-generate-config --root /mnt")
+          machine.succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2")
+          machine.copy_from_host(
+              "${ makeConfig {
+                    inherit bootLoader grubDevice grubIdentifier
+                            grubUseEfi extraConfig clevisTest;
+                  }
+              }",
+              "/mnt/etc/nixos/configuration.nix",
+          )
+          machine.copy_from_host("${pkgs.writeText "secret" "secret"}", "/mnt/etc/nixos/secret")
+
+      ${optionalString clevisTest ''
+        with subtest("Create the Clevis secret with Tang"):
+             machine.systemctl("start network-online.target")
+             machine.wait_for_unit("network-online.target")
+             machine.succeed('echo -n password | clevis encrypt sss \'{"t": 2, "pins": {"tpm2": {}, "tang": {"url": "http://192.168.1.2"}}}\' -y > /mnt/etc/nixos/clevis-secret.jwe')''}
+
+      ${optionalString clevisFallbackTest ''
+        with subtest("Shutdown Tang to check fallback to interactive prompt"):
+            tang.shutdown()
+      ''}
+
+      with subtest("Perform the installation"):
+          machine.succeed("nixos-install < /dev/null >&2")
+
+      with subtest("Do it again to make sure it's idempotent"):
+          machine.succeed("nixos-install < /dev/null >&2")
+
+      with subtest("Check that we can build things in nixos-enter"):
+          machine.succeed(
+              """
+              nixos-enter -- nix-build --option substitute false -E 'derivation {
+                  name = "t";
+                  builder = "/bin/sh";
+                  args = ["-c" "echo nixos-enter build > $out"];
+                  system = builtins.currentSystem;
+                  preferLocalBuild = true;
+              }'
+              """
+          )
+
+      ${postInstallCommands}
+
+      with subtest("Shutdown system after installation"):
+          machine.succeed("umount -R /mnt")
+          machine.succeed("sync")
+          machine.shutdown()
+
+      # Now see if we can boot the installation.
+      machine = create_machine_named("boot-after-install")
+
+      # For example to enter LUKS passphrase.
+      ${preBootCommands}
+
+      with subtest("Assert that /boot get mounted"):
+          machine.wait_for_unit("local-fs.target")
+          ${if bootLoader == "grub"
+              then ''machine.succeed("test -e /boot/grub")''
+              else ''machine.succeed("test -e /boot/loader/loader.conf")''
+          }
+
+      with subtest("Check whether /root has correct permissions"):
+          assert "700" in machine.succeed("stat -c '%a' /root")
+
+      with subtest("Assert swap device got activated"):
+          # uncomment once https://bugs.freedesktop.org/show_bug.cgi?id=86930 is resolved
+          machine.wait_for_unit("swap.target")
+          machine.succeed("cat /proc/swaps | grep -q /dev")
+
+      with subtest("Check that the store is in good shape"):
+          machine.succeed("nix-store --verify --check-contents >&2")
+
+      with subtest("Check whether the channel works"):
+          machine.succeed("nix-env -iA nixos.procps >&2")
+          assert ".nix-profile" in machine.succeed("type -tP ps | tee /dev/stderr")
+
+      with subtest(
+          "Check that the daemon works, and that non-root users can run builds "
+          "(this will build a new profile generation through the daemon)"
+      ):
+          machine.succeed("su alice -l -c 'nix-env -iA nixos.procps' >&2")
+
+      with subtest("Configure system with writable Nix store on next boot"):
+          # we're not using copy_from_host here because the installer image
+          # doesn't know about the host-guest sharing mechanism.
+          machine.copy_from_host_via_shell(
+              "${ makeConfig {
+                    inherit bootLoader grubDevice grubIdentifier
+                            grubUseEfi extraConfig clevisTest;
+                    forceGrubReinstallCount = 1;
+                  }
+              }",
+              "/etc/nixos/configuration.nix",
+          )
+
+      with subtest("Check whether nixos-rebuild works"):
+          machine.succeed("nixos-rebuild switch >&2")
+
+      # FIXME: Nix 2.4 broke nixos-option, someone has to fix it.
+      # with subtest("Test nixos-option"):
+      #     kernel_modules = machine.succeed("nixos-option boot.initrd.kernelModules")
+      #     assert "virtio_console" in kernel_modules
+      #     assert "List of modules" in kernel_modules
+      #     assert "qemu-guest.nix" in kernel_modules
+
+      machine.shutdown()
+
+      # Check whether a writable store build works
+      machine = create_machine_named("rebuild-switch")
+      ${preBootCommands}
+      machine.wait_for_unit("multi-user.target")
+
+      # we're not using copy_from_host here because the installer image
+      # doesn't know about the host-guest sharing mechanism.
+      machine.copy_from_host_via_shell(
+          "${ makeConfig {
+                inherit bootLoader grubDevice grubIdentifier
+                grubUseEfi extraConfig clevisTest;
+                forceGrubReinstallCount = 2;
+              }
+          }",
+          "/etc/nixos/configuration.nix",
+      )
+      machine.succeed("nixos-rebuild boot >&2")
+      machine.shutdown()
+
+      # And just to be sure, check that the machine still boots after
+      # "nixos-rebuild switch".
+      machine = create_machine_named("boot-after-rebuild-switch")
+      ${preBootCommands}
+      machine.wait_for_unit("network.target")
+
+      # Sanity check, is it the configuration.nix we generated?
+      hostname = machine.succeed("hostname").strip()
+      assert hostname == "thatworked"
+
+      ${postBootCommands}
+      machine.shutdown()
+
+      # Tests for validating clone configuration entries in grub menu
+    ''
+    + optionalString testSpecialisationConfig ''
+      # Reboot Machine
+      machine = create_machine_named("clone-default-config")
+      ${preBootCommands}
+      machine.wait_for_unit("multi-user.target")
+
+      with subtest("Booted configuration name should be 'Home'"):
+          # This is not the name that shows in the grub menu.
+          # The default configuration is always shown as "Default"
+          machine.succeed("cat /run/booted-system/configuration-name >&2")
+          assert "Home" in machine.succeed("cat /run/booted-system/configuration-name")
+
+      with subtest("We should **not** find a file named /etc/gitconfig"):
+          machine.fail("test -e /etc/gitconfig")
+
+      with subtest("Set grub to boot the second configuration"):
+          machine.succeed("grub-reboot 1")
+
+      ${postBootCommands}
+      machine.shutdown()
+
+      # Reboot Machine
+      machine = create_machine_named("clone-alternate-config")
+      ${preBootCommands}
+
+      machine.wait_for_unit("multi-user.target")
+      with subtest("Booted configuration name should be Work"):
+          machine.succeed("cat /run/booted-system/configuration-name >&2")
+          assert "Work" in machine.succeed("cat /run/booted-system/configuration-name")
+
+      with subtest("We should find a file named /etc/gitconfig"):
+          machine.succeed("test -e /etc/gitconfig")
+
+      ${postBootCommands}
+      machine.shutdown()
+    ''
+    + optionalString testFlakeSwitch ''
+      ${preBootCommands}
+      machine.start()
+
+      with subtest("Configure system with flake"):
+        # TODO: evaluate as user?
+        machine.succeed("""
+          mkdir /root/my-config
+          mv /etc/nixos/hardware-configuration.nix /root/my-config/
+          mv /etc/nixos/secret /root/my-config/
+          rm /etc/nixos/configuration.nix
+        """)
+        machine.copy_from_host_via_shell(
+          "${makeConfig {
+               inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
+               forceGrubReinstallCount = 1;
+               flake = true;
+            }}",
+          "/root/my-config/configuration.nix",
+        )
+        machine.copy_from_host_via_shell(
+          "${./installer/flake.nix}",
+          "/root/my-config/flake.nix",
+        )
+        machine.succeed("""
+          # for some reason the image does not have `pkgs.path`, so
+          # we use readlink to find a Nixpkgs source.
+          pkgs=$(readlink -f /nix/var/nix/profiles/per-user/root/channels)/nixos
+          if ! [[ -e $pkgs/pkgs/top-level/default.nix ]]; then
+            echo 1>&2 "$pkgs does not seem to be a nixpkgs source. Please fix the test so that pkgs points to a nixpkgs source.";
+            exit 1;
+          fi
+          sed -e s^@nixpkgs@^$pkgs^ -i /root/my-config/flake.nix
+        """)
+
+      with subtest("Switch to flake based config"):
+        machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
+
+      ${postBootCommands}
+      machine.shutdown()
+
+      ${preBootCommands}
+      machine.start()
+
+      machine.wait_for_unit("multi-user.target")
+
+      with subtest("nix-channel command is not available anymore"):
+        machine.succeed("! which nix-channel")
+
+      # Note that the channel profile is still present on disk, but configured
+      # not to be used.
+      with subtest("builtins.nixPath is now empty"):
+        machine.succeed("""
+          [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]]
+        """)
+
+      with subtest("<nixpkgs> does not resolve"):
+        machine.succeed("""
+          ! nix-instantiate '<nixpkgs>' --eval --expr
+        """)
+
+      with subtest("Evaluate flake config in fresh env without nix-channel"):
+        machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
+
+      with subtest("Evaluate flake config in fresh env without channel profiles"):
+        machine.succeed("""
+          (
+            exec 1>&2
+            rm -v /root/.nix-channels
+            rm -vrf ~/.nix-defexpr
+            rm -vrf /nix/var/nix/profiles/per-user/root/channels*
+          )
+        """)
+        machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
+
+      ${postBootCommands}
+      machine.shutdown()
+    '';
+
+
+  makeInstallerTest = name:
+    { createPartitions
+    , postInstallCommands ? "", preBootCommands ? "", postBootCommands ? ""
+    , extraConfig ? ""
+    , extraInstallerConfig ? {}
+    , bootLoader ? "grub" # either "grub" or "systemd-boot"
+    , grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
+    , enableOCR ? false, meta ? {}
+    , testSpecialisationConfig ? false
+    , testFlakeSwitch ? false
+    , clevisTest ? false
+    , clevisFallbackTest ? false
+    }:
+    makeTest {
+      inherit enableOCR;
+      name = "installer-" + name;
+      meta = {
+        # put global maintainers here, individuals go into makeInstallerTest fkt call
+        maintainers = (meta.maintainers or []);
+      };
+      nodes = {
+
+        # The configuration of the machine used to run "nixos-install".
+        machine = { pkgs, ... }: {
+          imports = [
+            ../modules/profiles/installation-device.nix
+            ../modules/profiles/base.nix
+            extraInstallerConfig
+            ./common/auto-format-root-device.nix
+          ];
+
+          # In systemdStage1, also automatically format the device backing the
+          # root filesystem.
+          virtualisation.fileSystems."/".autoFormat = systemdStage1;
+
+          # builds stuff in the VM, needs more juice
+          virtualisation.diskSize = 8 * 1024;
+          virtualisation.cores = 8;
+          virtualisation.memorySize = 1536;
+
+          boot.initrd.systemd.enable = systemdStage1;
+
+          # Use a small /dev/vdb as the root disk for the
+          # installer. This ensures the target disk (/dev/vda) is
+          # the same during and after installation.
+          virtualisation.emptyDiskImages = [ 512 ];
+          virtualisation.rootDevice = "/dev/vdb";
+          virtualisation.bootLoaderDevice = "/dev/vda";
+          virtualisation.qemu.diskInterface = "virtio";
+          virtualisation.qemu.options = mkIf (clevisTest) [
+            "-chardev socket,id=chrtpm,path=$NIX_BUILD_TOP/swtpm-sock"
+            "-tpmdev emulator,id=tpm0,chardev=chrtpm"
+            "-device tpm-tis,tpmdev=tpm0"
+          ];
+          # We don't want to have any networking in the guest apart from the clevis tests.
+          virtualisation.vlans = mkIf (!clevisTest) [];
+
+          boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true;
+
+          hardware.enableAllFirmware = mkForce false;
+
+          # The test cannot access the network, so any packages we
+          # need must be included in the VM.
+          system.extraDependencies = with pkgs; [
+            bintools
+            brotli
+            brotli.dev
+            brotli.lib
+            desktop-file-utils
+            docbook5
+            docbook_xsl_ns
+            kbd.dev
+            kmod.dev
+            libarchive.dev
+            libxml2.bin
+            libxslt.bin
+            nixos-artwork.wallpapers.simple-dark-gray-bottom
+            ntp
+            perlPackages.ListCompare
+            perlPackages.XMLLibXML
+            # make-options-doc/default.nix
+            (python3.withPackages (p: [ p.mistune ]))
+            shared-mime-info
+            sudo
+            texinfo
+            unionfs-fuse
+            xorg.lndir
+
+            # add curl so that rather than seeing the test attempt to download
+            # curl's tarball, we see what it's trying to download
+            curl
+          ]
+          ++ optionals (bootLoader == "grub") (let
+            zfsSupport = extraInstallerConfig.boot.supportedFilesystems.zfs or false;
+          in [
+            (pkgs.grub2.override { inherit zfsSupport; })
+            (pkgs.grub2_efi.override { inherit zfsSupport; })
+          ]) ++ optionals clevisTest [ pkgs.klibc ];
+
+          nix.settings = {
+            substituters = mkForce [];
+            hashed-mirrors = null;
+            connect-timeout = 1;
+          };
+        };
+
+      } // optionalAttrs clevisTest {
+        tang = {
+          services.tang = {
+            enable = true;
+            listenStream = [ "80" ];
+            ipAddressAllow = [ "192.168.1.0/24" ];
+          };
+          networking.firewall.allowedTCPPorts = [ 80 ];
+        };
+      };
+
+      testScript = testScriptFun {
+        inherit bootLoader createPartitions postInstallCommands preBootCommands postBootCommands
+                grubDevice grubIdentifier grubUseEfi extraConfig
+                testSpecialisationConfig testFlakeSwitch clevisTest clevisFallbackTest;
+      };
+    };
+
+    makeLuksRootTest = name: luksFormatOpts: makeInstallerTest name {
+      createPartitions = ''
+        machine.succeed(
+            "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+            + " mkpart primary ext2 1M 100MB"  # /boot
+            + " mkpart primary linux-swap 100M 1024M"
+            + " mkpart primary 1024M -1s",  # LUKS
+            "udevadm settle",
+            "mkswap /dev/vda2 -L swap",
+            "swapon -L swap",
+            "modprobe dm_mod dm_crypt",
+            "echo -n supersecret | cryptsetup luksFormat ${luksFormatOpts} -q /dev/vda3 -",
+            "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda3 cryptroot",
+            "mkfs.ext3 -L nixos /dev/mapper/cryptroot",
+            "mount LABEL=nixos /mnt",
+            "mkfs.ext3 -L boot /dev/vda1",
+            "mkdir -p /mnt/boot",
+            "mount LABEL=boot /mnt/boot",
+        )
+      '';
+      extraConfig = ''
+        boot.kernelParams = lib.mkAfter [ "console=tty0" ];
+      '';
+      enableOCR = true;
+      preBootCommands = ''
+        machine.start()
+        machine.wait_for_text("[Pp]assphrase for")
+        machine.send_chars("supersecret\n")
+      '';
+    };
+
+  # The (almost) simplest partitioning scheme: a swap partition and
+  # one big filesystem partition.
+  simple-test-config = {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          + " mkpart primary linux-swap 1M 1024M"
+          + " mkpart primary ext2 1024M -1s",
+          "udevadm settle",
+          "mkswap /dev/vda1 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/vda2",
+          "mount LABEL=nixos /mnt",
+      )
+    '';
+  };
+
+  simple-test-config-flake = simple-test-config // {
+    testFlakeSwitch = true;
+  };
+
+  simple-uefi-grub-config = {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+          + " mkpart ESP fat32 1M 100MiB"  # /boot
+          + " set 1 boot on"
+          + " mkpart primary linux-swap 100MiB 1024MiB"
+          + " mkpart primary ext2 1024MiB -1MiB",  # /
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/vda3",
+          "mount LABEL=nixos /mnt",
+          "mkfs.vfat -n BOOT /dev/vda1",
+          "mkdir -p /mnt/boot",
+          "mount LABEL=BOOT /mnt/boot",
+      )
+    '';
+    bootLoader = "grub";
+    grubUseEfi = true;
+  };
+
+  specialisation-test-extraconfig = {
+    extraConfig = ''
+      environment.systemPackages = [ pkgs.grub2 ];
+      boot.loader.grub.configurationName = "Home";
+      specialisation.work.configuration = {
+        boot.loader.grub.configurationName = lib.mkForce "Work";
+
+        environment.etc = {
+          "gitconfig".text = "
+            [core]
+              gitproxy = none for work.com
+              ";
+        };
+      };
+    '';
+    testSpecialisationConfig = true;
+  };
+  # disable zfs so we can support latest kernel if needed
+  no-zfs-module = {
+    nixpkgs.overlays = [(final: super: {
+      zfs = super.zfs.overrideAttrs(_: {meta.platforms = [];});}
+    )];
+  };
+
+ mkClevisBcachefsTest = { fallback ? false }: makeInstallerTest "clevis-bcachefs${optionalString fallback "-fallback"}" {
+    clevisTest = true;
+    clevisFallbackTest = fallback;
+    enableOCR = fallback;
+    extraInstallerConfig = {
+      imports = [ no-zfs-module ];
+      boot.supportedFilesystems = [ "bcachefs" ];
+      environment.systemPackages = with pkgs; [ keyutils clevis ];
+    };
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"
+        + " mkpart primary linux-swap 100M 1024M"
+        + " mkpart primary 1024M -1s",
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "keyctl link @u @s",
+        "echo -n password | mkfs.bcachefs -L root --encrypted /dev/vda3",
+        "echo -n password | bcachefs unlock /dev/vda3",
+        "echo -n password | mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount LABEL=boot /mnt/boot",
+        "udevadm settle")
+    '';
+    extraConfig = ''
+      boot.initrd.clevis.devices."/dev/vda3".secretFile = "/etc/nixos/clevis-secret.jwe";
+
+      # We override what nixos-generate-config has generated because we do
+      # not know the UUID in advance.
+      fileSystems."/" = lib.mkForce { device = "/dev/vda3"; fsType = "bcachefs"; };
+    '';
+    preBootCommands = ''
+      tpm = Tpm()
+      tpm.check()
+    '' + optionalString fallback ''
+      machine.start()
+      machine.wait_for_text("enter passphrase for")
+      machine.send_chars("password\n")
+    '';
+  };
+
+  mkClevisLuksTest = { fallback ? false }: makeInstallerTest "clevis-luks${optionalString fallback "-fallback"}" {
+    clevisTest = true;
+    clevisFallbackTest = fallback;
+    enableOCR = fallback;
+    extraInstallerConfig = {
+      environment.systemPackages = with pkgs; [ clevis ];
+    };
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"
+        + " mkpart primary linux-swap 100M 1024M"
+        + " mkpart primary 1024M -1s",
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "modprobe dm_mod dm_crypt",
+        "echo -n password | cryptsetup luksFormat -q /dev/vda3 -",
+        "echo -n password | cryptsetup luksOpen --key-file - /dev/vda3 crypt-root",
+        "mkfs.ext3 -L nixos /dev/mapper/crypt-root",
+        "mount LABEL=nixos /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount LABEL=boot /mnt/boot",
+        "udevadm settle")
+    '';
+    extraConfig = ''
+      boot.initrd.clevis.devices."crypt-root".secretFile = "/etc/nixos/clevis-secret.jwe";
+    '';
+    preBootCommands = ''
+      tpm = Tpm()
+      tpm.check()
+    '' + optionalString fallback ''
+      machine.start()
+      ${if systemdStage1 then ''
+      machine.wait_for_text("Please enter")
+      '' else ''
+      machine.wait_for_text("Passphrase for")
+      ''}
+      machine.send_chars("password\n")
+    '';
+  };
+
+  mkClevisZfsTest = { fallback ? false }: makeInstallerTest "clevis-zfs${optionalString fallback "-fallback"}" {
+    clevisTest = true;
+    clevisFallbackTest = fallback;
+    enableOCR = fallback;
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "zfs" ];
+      environment.systemPackages = with pkgs; [ clevis ];
+    };
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"
+        + " mkpart primary linux-swap 100M 1024M"
+        + " mkpart primary 1024M -1s",
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "zpool create -O mountpoint=legacy rpool /dev/vda3",
+        "echo -n password | zfs create"
+        + " -o encryption=aes-256-gcm -o keyformat=passphrase rpool/root",
+        "mount -t zfs rpool/root /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount LABEL=boot /mnt/boot",
+        "udevadm settle")
+    '';
+    extraConfig = ''
+      boot.initrd.clevis.devices."rpool/root".secretFile = "/etc/nixos/clevis-secret.jwe";
+      boot.zfs.requestEncryptionCredentials = true;
+
+
+      # Using by-uuid overrides the default of by-id, and is unique
+      # to the qemu disks, as they don't produce by-id paths for
+      # some reason.
+      boot.zfs.devNodes = "/dev/disk/by-uuid/";
+      networking.hostId = "00000000";
+    '';
+    preBootCommands = ''
+      tpm = Tpm()
+      tpm.check()
+    '' + optionalString fallback ''
+      machine.start()
+      ${if systemdStage1 then ''
+      machine.wait_for_text("Enter key for rpool/root")
+      '' else ''
+      machine.wait_for_text("Key load error")
+      ''}
+      machine.send_chars("password\n")
+    '';
+  };
+
+in {
+
+  # !!! `parted mkpart' seems to silently create overlapping partitions.
+
+
+  # The (almost) simplest partitioning scheme: a swap partition and
+  # one big filesystem partition.
+  simple = makeInstallerTest "simple" simple-test-config;
+
+  switchToFlake = makeInstallerTest "switch-to-flake" simple-test-config-flake;
+
+  # Test cloned configurations with the simple grub configuration
+  simpleSpecialised = makeInstallerTest "simpleSpecialised" (simple-test-config // specialisation-test-extraconfig);
+
+  # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
+  simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+          + " mkpart ESP fat32 1M 100MiB"  # /boot
+          + " set 1 boot on"
+          + " mkpart primary linux-swap 100MiB 1024MiB"
+          + " mkpart primary ext2 1024MiB -1MiB",  # /
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/vda3",
+          "mount LABEL=nixos /mnt",
+          "mkfs.vfat -n BOOT /dev/vda1",
+          "mkdir -p /mnt/boot",
+          "mount LABEL=BOOT /mnt/boot",
+      )
+    '';
+    bootLoader = "systemd-boot";
+  };
+
+  simpleUefiGrub = makeInstallerTest "simpleUefiGrub" simple-uefi-grub-config;
+
+  # Test cloned configurations with the uefi grub configuration
+  simpleUefiGrubSpecialisation = makeInstallerTest "simpleUefiGrubSpecialisation" (simple-uefi-grub-config // specialisation-test-extraconfig);
+
+  # Same as the previous, but now with a separate /boot partition.
+  separateBoot = makeInstallerTest "separateBoot" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart primary linux-swap 100MB 1024M"
+          + " mkpart primary ext2 1024M -1s",  # /
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/vda3",
+          "mount LABEL=nixos /mnt",
+          "mkfs.ext3 -L boot /dev/vda1",
+          "mkdir -p /mnt/boot",
+          "mount LABEL=boot /mnt/boot",
+      )
+    '';
+  };
+
+  # Same as the previous, but with fat32 /boot.
+  separateBootFat = makeInstallerTest "separateBootFat" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart primary linux-swap 100MB 1024M"
+          + " mkpart primary ext2 1024M -1s",  # /
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/vda3",
+          "mount LABEL=nixos /mnt",
+          "mkfs.vfat -n BOOT /dev/vda1",
+          "mkdir -p /mnt/boot",
+          "mount LABEL=BOOT /mnt/boot",
+      )
+    '';
+  };
+
+  # Same as the previous, but with ZFS /boot.
+  separateBootZfs = makeInstallerTest "separateBootZfs" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "zfs" ];
+    };
+
+    extraConfig = ''
+      # Using by-uuid overrides the default of by-id, and is unique
+      # to the qemu disks, as they don't produce by-id paths for
+      # some reason.
+      boot.zfs.devNodes = "/dev/disk/by-uuid/";
+      networking.hostId = "00000000";
+    '';
+
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          + " mkpart primary ext2 1M 256MB"   # /boot
+          + " mkpart primary linux-swap 256MB 1280M"
+          + " mkpart primary ext2 1280M -1s", # /
+          "udevadm settle",
+
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+
+          "mkfs.ext4 -L nixos /dev/vda3",
+          "mount LABEL=nixos /mnt",
+
+          # Use as many ZFS features as possible to verify that GRUB can handle them
+          "zpool create"
+            " -o compatibility=grub2"
+            " -O utf8only=on"
+            " -O normalization=formD"
+            " -O compression=lz4"      # Activate the lz4_compress feature
+            " -O xattr=sa"
+            " -O acltype=posixacl"
+            " bpool /dev/vda1",
+          "zfs create"
+            " -o recordsize=1M"        # Prepare activating the large_blocks feature
+            " -o mountpoint=legacy"
+            " -o relatime=on"
+            " -o quota=1G"
+            " -o filesystem_limit=100" # Activate the filesystem_limits features
+            " bpool/boot",
+
+          # Snapshotting the top-level dataset would trigger a bug in GRUB2: https://github.com/openzfs/zfs/issues/13873
+          "zfs snapshot bpool/boot@snap-1",                     # Prepare activating the livelist and bookmarks features
+          "zfs clone bpool/boot@snap-1 bpool/test",             # Activate the livelist feature
+          "zfs bookmark bpool/boot@snap-1 bpool/boot#bookmark", # Activate the bookmarks feature
+          "zpool checkpoint bpool",                             # Activate the zpool_checkpoint feature
+          "mkdir -p /mnt/boot",
+          "mount -t zfs bpool/boot /mnt/boot",
+          "touch /mnt/boot/empty",                              # Activate zilsaxattr feature
+          "dd if=/dev/urandom of=/mnt/boot/test bs=1M count=1", # Activate the large_blocks feature
+
+          # Print out all enabled and active ZFS features (and some other stuff)
+          "sync /mnt/boot",
+          "zpool get all bpool >&2",
+
+          # Abort early if GRUB2 doesn't like the disks
+          "grub-probe --target=device /mnt/boot >&2",
+      )
+    '';
+
+    # umount & export bpool before shutdown
+    # this is a fix for "cannot import 'bpool': pool was previously in use from another system."
+    postInstallCommands = ''
+      machine.succeed("umount /mnt/boot")
+      machine.succeed("zpool export bpool")
+    '';
+  };
+
+  # zfs on / with swap
+  zfsroot = makeInstallerTest "zfs-root" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "zfs" ];
+    };
+
+    extraConfig = ''
+      boot.supportedFilesystems = [ "zfs" ];
+
+      # Using by-uuid overrides the default of by-id, and is unique
+      # to the qemu disks, as they don't produce by-id paths for
+      # some reason.
+      boot.zfs.devNodes = "/dev/disk/by-uuid/";
+      networking.hostId = "00000000";
+    '';
+
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          + " mkpart primary 1M 100MB"  # /boot
+          + " mkpart primary linux-swap 100M 1024M"
+          + " mkpart primary 1024M -1s", # rpool
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "zpool create rpool /dev/vda3",
+          "zfs create -o mountpoint=legacy rpool/root",
+          "mount -t zfs rpool/root /mnt",
+          "zfs create -o mountpoint=legacy rpool/root/usr",
+          "mkdir /mnt/usr",
+          "mount -t zfs rpool/root/usr /mnt/usr",
+          "mkfs.vfat -n BOOT /dev/vda1",
+          "mkdir /mnt/boot",
+          "mount LABEL=BOOT /mnt/boot",
+          "udevadm settle",
+      )
+    '';
+  };
+
+  # Create two physical LVM partitions combined into one volume group
+  # that contains the logical swap and root partitions.
+  lvm = makeInstallerTest "lvm" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          + " mkpart primary 1M 2048M"  # PV1
+          + " set 1 lvm on"
+          + " mkpart primary 2048M -1s"  # PV2
+          + " set 2 lvm on",
+          "udevadm settle",
+          "pvcreate /dev/vda1 /dev/vda2",
+          "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
+          "lvcreate --size 1G --name swap MyVolGroup",
+          "lvcreate --size 6G --name nixos MyVolGroup",
+          "mkswap -f /dev/MyVolGroup/swap -L swap",
+          "swapon -L swap",
+          "mkfs.xfs -L nixos /dev/MyVolGroup/nixos",
+          "mount LABEL=nixos /mnt",
+      )
+    '';
+  };
+
+  # Boot off an encrypted root partition with the default LUKS header format
+  luksroot = makeLuksRootTest "luksroot-format1" "";
+
+  # Boot off an encrypted root partition with LUKS1 format
+  luksroot-format1 = makeLuksRootTest "luksroot-format1" "--type=LUKS1";
+
+  # Boot off an encrypted root partition with LUKS2 format
+  luksroot-format2 = makeLuksRootTest "luksroot-format2" "--type=LUKS2";
+
+  # Test whether opening encrypted filesystem with keyfile
+  # Checks for regression of missing cryptsetup, when no luks device without
+  # keyfile is configured
+  encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart primary linux-swap 100M 1024M"
+          + " mkpart primary 1024M 1280M"  # LUKS with keyfile
+          + " mkpart primary 1280M -1s",
+          "udevadm settle",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/vda4",
+          "mount LABEL=nixos /mnt",
+          "mkfs.ext3 -L boot /dev/vda1",
+          "mkdir -p /mnt/boot",
+          "mount LABEL=boot /mnt/boot",
+          "modprobe dm_mod dm_crypt",
+          "echo -n supersecret > /mnt/keyfile",
+          "cryptsetup luksFormat -q /dev/vda3 --key-file /mnt/keyfile",
+          "cryptsetup luksOpen --key-file /mnt/keyfile /dev/vda3 crypt",
+          "mkfs.ext3 -L test /dev/mapper/crypt",
+          "cryptsetup luksClose crypt",
+          "mkdir -p /mnt/test",
+      )
+    '';
+    extraConfig = ''
+      fileSystems."/test" = {
+        device = "/dev/disk/by-label/test";
+        fsType = "ext3";
+        encrypted.enable = true;
+        encrypted.blkDev = "/dev/vda3";
+        encrypted.label = "crypt";
+        encrypted.keyFile = "/${if systemdStage1 then "sysroot" else "mnt-root"}/keyfile";
+      };
+    '';
+  };
+
+  # Full disk encryption (root, kernel and initrd encrypted) using GRUB, GPT/UEFI,
+  # LVM-on-LUKS and a keyfile in initrd.secrets to enter the passphrase once
+  fullDiskEncryption = makeInstallerTest "fullDiskEncryption" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+          + " mkpart ESP fat32 1M 100MiB"  # /boot/efi
+          + " set 1 boot on"
+          + " mkpart primary ext2 1024MiB -1MiB",  # LUKS
+          "udevadm settle",
+          "modprobe dm_mod dm_crypt",
+          "dd if=/dev/random of=luks.key bs=256 count=1",
+          "echo -n supersecret | cryptsetup luksFormat -q --pbkdf-force-iterations 1000 --type luks1 /dev/vda2 -",
+          "echo -n supersecret | cryptsetup luksAddKey -q --pbkdf-force-iterations 1000 --key-file - /dev/vda2 luks.key",
+          "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda2 crypt",
+          "pvcreate /dev/mapper/crypt",
+          "vgcreate crypt /dev/mapper/crypt",
+          "lvcreate -L 100M -n swap crypt",
+          "lvcreate -l '100%FREE' -n nixos crypt",
+          "mkfs.vfat -n efi /dev/vda1",
+          "mkfs.ext4 -L nixos /dev/crypt/nixos",
+          "mkswap -L swap /dev/crypt/swap",
+          "mount LABEL=nixos /mnt",
+          "mkdir -p /mnt/{etc/nixos,boot/efi}",
+          "mount LABEL=efi /mnt/boot/efi",
+          "swapon -L swap",
+          "mv luks.key /mnt/etc/nixos/"
+      )
+    '';
+    bootLoader = "grub";
+    grubUseEfi = true;
+    extraConfig = ''
+      boot.loader.grub.enableCryptodisk = true;
+      boot.loader.efi.efiSysMountPoint = "/boot/efi";
+
+      boot.initrd.secrets."/luks.key" = ./luks.key;
+      boot.initrd.luks.devices.crypt =
+        { device  = "/dev/vda2";
+          keyFile = "/luks.key";
+        };
+    '';
+    enableOCR = true;
+    preBootCommands = ''
+      machine.start()
+      machine.wait_for_text("Enter passphrase for")
+      machine.send_chars("supersecret\n")
+    '';
+  };
+
+  swraid = makeInstallerTest "swraid" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda --"
+          + " mklabel msdos"
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart extended 100M -1s"
+          + " mkpart logical 102M 3102M"  # md0 (root), first device
+          + " mkpart logical 3103M 6103M"  # md0 (root), second device
+          + " mkpart logical 6104M 6360M"  # md1 (swap), first device
+          + " mkpart logical 6361M 6617M",  # md1 (swap), second device
+          "udevadm settle",
+          "ls -l /dev/vda* >&2",
+          "cat /proc/partitions >&2",
+          "udevadm control --stop-exec-queue",
+          "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 "
+          + "--raid-devices=2 /dev/vda5 /dev/vda6",
+          "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 "
+          + "--raid-devices=2 /dev/vda7 /dev/vda8",
+          "udevadm control --start-exec-queue",
+          "udevadm settle",
+          "mkswap -f /dev/md1 -L swap",
+          "swapon -L swap",
+          "mkfs.ext3 -L nixos /dev/md0",
+          "mount LABEL=nixos /mnt",
+          "mkfs.ext3 -L boot /dev/vda1",
+          "mkdir /mnt/boot",
+          "mount LABEL=boot /mnt/boot",
+          "udevadm settle",
+      )
+    '';
+    preBootCommands = ''
+      machine.start()
+      machine.fail("dmesg | grep 'immediate safe mode'")
+    '';
+  };
+
+  bcache = makeInstallerTest "bcache" {
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda --"
+          + " mklabel msdos"
+          + " mkpart primary ext2 1M 100MB"  # /boot
+          + " mkpart primary 100MB 512MB  "  # swap
+          + " mkpart primary 512MB 1024MB"  # Cache (typically SSD)
+          + " mkpart primary 1024MB -1s ",  # Backing device (typically HDD)
+          "modprobe bcache",
+          "udevadm settle",
+          "make-bcache -B /dev/vda4 -C /dev/vda3",
+          "udevadm settle",
+          "mkfs.ext3 -L nixos /dev/bcache0",
+          "mount LABEL=nixos /mnt",
+          "mkfs.ext3 -L boot /dev/vda1",
+          "mkdir /mnt/boot",
+          "mount LABEL=boot /mnt/boot",
+          "mkswap -f /dev/vda2 -L swap",
+          "swapon -L swap",
+      )
+    '';
+  };
+
+  bcachefsSimple = makeInstallerTest "bcachefs-simple" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+      imports = [ no-zfs-module ];
+    };
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.bcachefs -L root /dev/vda3",
+        "mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
+  bcachefsEncrypted = makeInstallerTest "bcachefs-encrypted" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+
+      # disable zfs so we can support latest kernel if needed
+      imports = [ no-zfs-module ];
+
+      environment.systemPackages = with pkgs; [ keyutils ];
+    };
+
+    extraConfig = ''
+      boot.kernelParams = lib.mkAfter [ "console=tty0" ];
+    '';
+
+    enableOCR = true;
+    preBootCommands = ''
+      machine.start()
+      # Enter it wrong once
+      machine.wait_for_text("enter passphrase for ")
+      machine.send_chars("wrong\n")
+      # Then enter it right.
+      machine.wait_for_text("enter passphrase for ")
+      machine.send_chars("password\n")
+    '';
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "echo password | mkfs.bcachefs -L root --encrypted /dev/vda3",
+        "echo password | bcachefs unlock -k session /dev/vda3",
+        "echo password | mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
+  bcachefsMulti = makeInstallerTest "bcachefs-multi" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "bcachefs" ];
+
+      # disable zfs so we can support latest kernel if needed
+      imports = [ no-zfs-module ];
+    };
+
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"          # /boot
+        + " mkpart primary linux-swap 100M 1024M"  # swap
+        + " mkpart primary 1024M 4096M"            # /
+        + " mkpart primary 4096M -1s",             # /
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "mkfs.bcachefs -L root --metadata_replicas 2 --foreground_target ssd --promote_target ssd --background_target hdd --label ssd /dev/vda3 --label hdd /dev/vda4",
+        "mount -t bcachefs /dev/vda3:/dev/vda4 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot",
+      )
+    '';
+  };
+
+  # Test using labels to identify volumes in grub
+  simpleLabels = makeInstallerTest "simpleLabels" {
+    createPartitions = ''
+      machine.succeed(
+          "sgdisk -Z /dev/vda",
+          "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.ext4 -L root /dev/vda3",
+          "mount LABEL=root /mnt",
+      )
+    '';
+    grubIdentifier = "label";
+  };
+
+  # Test using the provided disk name within grub
+  # TODO: Fix udev so the symlinks are unneeded in /dev/disks
+  simpleProvided = makeInstallerTest "simpleProvided" {
+    createPartitions = ''
+      uuid = "$(blkid -s UUID -o value /dev/vda2)"
+      machine.succeed(
+          "sgdisk -Z /dev/vda",
+          "sgdisk -n 1:0:+1M -n 2:0:+100M -n 3:0:+1G -N 4 -t 1:ef02 -t 2:8300 "
+          + "-t 3:8200 -t 4:8300 -c 2:boot -c 4:root /dev/vda",
+          "mkswap /dev/vda3 -L swap",
+          "swapon -L swap",
+          "mkfs.ext4 -L boot /dev/vda2",
+          "mkfs.ext4 -L root /dev/vda4",
+      )
+      machine.execute(f"ln -s ../../vda2 /dev/disk/by-uuid/{uuid}")
+      machine.execute("ln -s ../../vda4 /dev/disk/by-label/root")
+      machine.succeed(
+          "mount /dev/disk/by-label/root /mnt",
+          "mkdir /mnt/boot",
+          f"mount /dev/disk/by-uuid/{uuid} /mnt/boot",
+      )
+    '';
+    grubIdentifier = "provided";
+  };
+
+  # Simple btrfs grub testing
+  btrfsSimple = makeInstallerTest "btrfsSimple" {
+    createPartitions = ''
+      machine.succeed(
+          "sgdisk -Z /dev/vda",
+          "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.btrfs -L root /dev/vda3",
+          "mount LABEL=root /mnt",
+      )
+    '';
+  };
+
+  # Test to see if we can detect /boot and /nix on subvolumes
+  btrfsSubvols = makeInstallerTest "btrfsSubvols" {
+    createPartitions = ''
+      machine.succeed(
+          "sgdisk -Z /dev/vda",
+          "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.btrfs -L root /dev/vda3",
+          "btrfs device scan",
+          "mount LABEL=root /mnt",
+          "btrfs subvol create /mnt/boot",
+          "btrfs subvol create /mnt/nixos",
+          "btrfs subvol create /mnt/nixos/default",
+          "umount /mnt",
+          "mount -o defaults,subvol=nixos/default LABEL=root /mnt",
+          "mkdir /mnt/boot",
+          "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
+      )
+    '';
+  };
+
+  # Test to see if we can detect default and aux subvolumes correctly
+  btrfsSubvolDefault = makeInstallerTest "btrfsSubvolDefault" {
+    createPartitions = ''
+      machine.succeed(
+          "sgdisk -Z /dev/vda",
+          "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.btrfs -L root /dev/vda3",
+          "btrfs device scan",
+          "mount LABEL=root /mnt",
+          "btrfs subvol create /mnt/badpath",
+          "btrfs subvol create /mnt/badpath/boot",
+          "btrfs subvol create /mnt/nixos",
+          "btrfs subvol set-default "
+          + "$(btrfs subvol list /mnt | grep 'nixos' | awk '{print $2}') /mnt",
+          "umount /mnt",
+          "mount -o defaults LABEL=root /mnt",
+          "mkdir -p /mnt/badpath/boot",  # Help ensure the detection mechanism
+          # is actually looking up subvolumes
+          "mkdir /mnt/boot",
+          "mount -o defaults,subvol=badpath/boot LABEL=root /mnt/boot",
+      )
+    '';
+  };
+
+  # Test to see if we can deal with subvols that need to be escaped in fstab
+  btrfsSubvolEscape = makeInstallerTest "btrfsSubvolEscape" {
+    createPartitions = ''
+      machine.succeed(
+          "sgdisk -Z /dev/vda",
+          "sgdisk -n 1:0:+1M -n 2:0:+1G -N 3 -t 1:ef02 -t 2:8200 -t 3:8300 -c 3:root /dev/vda",
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+          "mkfs.btrfs -L root /dev/vda3",
+          "btrfs device scan",
+          "mount LABEL=root /mnt",
+          "btrfs subvol create '/mnt/nixos in space'",
+          "btrfs subvol create /mnt/boot",
+          "umount /mnt",
+          "mount -o 'defaults,subvol=nixos in space' LABEL=root /mnt",
+          "mkdir /mnt/boot",
+          "mount -o defaults,subvol=boot LABEL=root /mnt/boot",
+      )
+    '';
+  };
+} // {
+  clevisBcachefs = mkClevisBcachefsTest { };
+  clevisBcachefsFallback = mkClevisBcachefsTest { fallback = true; };
+  clevisLuks = mkClevisLuksTest { };
+  clevisLuksFallback = mkClevisLuksTest { fallback = true; };
+  clevisZfs = mkClevisZfsTest { };
+  clevisZfsFallback = mkClevisZfsTest { fallback = true; };
+} // optionalAttrs systemdStage1 {
+  stratisRoot = makeInstallerTest "stratisRoot" {
+    createPartitions = ''
+      machine.succeed(
+        "sgdisk --zap-all /dev/vda",
+        "sgdisk --new=1:0:+100M --typecode=0:ef00 /dev/vda", # /boot
+        "sgdisk --new=2:0:+1G --typecode=0:8200 /dev/vda", # swap
+        "sgdisk --new=3:0:+5G --typecode=0:8300 /dev/vda", # /
+        "udevadm settle",
+
+        "mkfs.vfat /dev/vda1",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "stratis pool create my-pool /dev/vda3",
+        "stratis filesystem create my-pool nixos",
+        "udevadm settle",
+
+        "mount /dev/stratis/my-pool/nixos /mnt",
+        "mkdir -p /mnt/boot",
+        "mount /dev/vda1 /mnt/boot"
+      )
+    '';
+    bootLoader = "systemd-boot";
+    extraInstallerConfig = { modulesPath, ...}: {
+      config = {
+        services.stratis.enable = true;
+        environment.systemPackages = [
+          pkgs.stratis-cli
+          pkgs.thin-provisioning-tools
+          pkgs.lvm2.bin
+          pkgs.stratisd.initrd
+        ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/installer/flake.nix b/nixpkgs/nixos/tests/installer/flake.nix
new file mode 100644
index 000000000000..4bbef44e34fc
--- /dev/null
+++ b/nixpkgs/nixos/tests/installer/flake.nix
@@ -0,0 +1,20 @@
+# This file gets copied into the installation
+
+{
+  # To keep things simple, we'll use an absolute path dependency here.
+  inputs.nixpkgs.url = "@nixpkgs@";
+
+  outputs = { nixpkgs, ... }: {
+
+    nixosConfigurations.xyz = nixpkgs.lib.nixosSystem {
+      modules = [
+        ./configuration.nix
+        ( nixpkgs + "/nixos/modules/testing/test-instrumentation.nix" )
+        {
+          # We don't need nix-channel anymore
+          nix.channel.enable = false;
+        }
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/intune.nix b/nixpkgs/nixos/tests/intune.nix
new file mode 100644
index 000000000000..41bf638d7661
--- /dev/null
+++ b/nixpkgs/nixos/tests/intune.nix
@@ -0,0 +1,56 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "intune";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ rhysmdnz ];
+  };
+  enableOCR = true;
+
+  nodes.machine =
+    { nodes, ... }:
+    let user = nodes.machine.users.users.alice;
+    in {
+      services.intune.enable=true;
+      services.gnome.gnome-keyring.enable = true;
+      imports = [ ./common/user-account.nix ./common/x11.nix ];
+      test-support.displayManager.auto.user = user.name;
+      environment = {
+        variables.DBUS_SESSION_BUS_ADDRESS = "unix:path=/run/user/${builtins.toString user.uid}/bus";
+      };
+    };
+  nodes.pam =
+    { nodes, ... }:
+    let user = nodes.machine.users.users.alice;
+    in {
+      services.intune.enable=true;
+      imports = [ ./common/user-account.nix ];
+    };
+
+  testScript = ''
+    start_all()
+
+    # Check System Daemons successfully start
+    machine.succeed("systemctl start microsoft-identity-device-broker.service")
+    machine.succeed("systemctl start intune-daemon.service")
+
+    # Check User Daemons and intune-portal execurtable works
+    # Going any further than starting it would require internet access and a microsoft account
+    machine.wait_for_x()
+    # TODO: This needs an unlocked user keychain before it will work
+    #machine.succeed("su - alice -c 'systemctl start --user microsoft-identity-broker.service'")
+    machine.succeed("su - alice -c 'systemctl start --user intune-agent.service'")
+    machine.succeed("su - alice -c intune-portal >&2 &")
+    machine.wait_for_text("Intune Agent")
+
+    # Check logging in creates password file
+    def login_as_alice():
+        pam.wait_until_tty_matches("1", "login: ")
+        pam.send_chars("alice\n")
+        pam.wait_until_tty_matches("1", "Password: ")
+        pam.send_chars("foobar\n")
+        pam.wait_until_tty_matches("1", "alice\@pam")
+
+    pam.wait_for_unit("multi-user.target")
+    login_as_alice()
+    pam.wait_for_file("/run/intune/1000/pwquality")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/invidious.nix b/nixpkgs/nixos/tests/invidious.nix
new file mode 100644
index 000000000000..e31cd87f6a00
--- /dev/null
+++ b/nixpkgs/nixos/tests/invidious.nix
@@ -0,0 +1,124 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "invidious";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ sbruder ];
+  };
+
+  nodes = {
+    postgres-tcp = { config, pkgs, ... }: {
+      services.postgresql = {
+        enable = true;
+        initialScript = pkgs.writeText "init-postgres-with-password" ''
+          CREATE USER invidious WITH PASSWORD 'correct horse battery staple';
+          CREATE DATABASE invidious WITH OWNER invidious;
+        '';
+        enableTCPIP = true;
+        authentication = ''
+          host invidious invidious samenet scram-sha-256
+        '';
+      };
+      networking.firewall.allowedTCPPorts = [ config.services.postgresql.port ];
+    };
+    machine = { config, lib, pkgs, ... }: {
+      services.invidious = {
+        enable = true;
+      };
+
+      specialisation = {
+        nginx.configuration = {
+          services.invidious = {
+            nginx.enable = true;
+            domain = "invidious.example.com";
+          };
+          services.nginx.virtualHosts."invidious.example.com" = {
+            forceSSL = false;
+            enableACME = false;
+          };
+          networking.hosts."127.0.0.1" = [ "invidious.example.com" ];
+        };
+        nginx-scale.configuration = {
+          services.invidious = {
+            nginx.enable = true;
+            domain = "invidious.example.com";
+            serviceScale = 3;
+          };
+          services.nginx.virtualHosts."invidious.example.com" = {
+            forceSSL = false;
+            enableACME = false;
+          };
+          networking.hosts."127.0.0.1" = [ "invidious.example.com" ];
+        };
+        nginx-scale-ytproxy.configuration = {
+          services.invidious = {
+            nginx.enable = true;
+            http3-ytproxy.enable = true;
+            domain = "invidious.example.com";
+            serviceScale = 3;
+          };
+          services.nginx.virtualHosts."invidious.example.com" = {
+            forceSSL = false;
+            enableACME = false;
+          };
+          networking.hosts."127.0.0.1" = [ "invidious.example.com" ];
+        };
+        postgres-tcp.configuration = {
+          services.invidious = {
+            database = {
+              createLocally = false;
+              host = "postgres-tcp";
+              passwordFile = toString (pkgs.writeText "database-password" "correct horse battery staple");
+            };
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    def curl_assert_status_code(url, code, form=None):
+        assert int(machine.succeed(f"curl -s -o /dev/null -w %{{http_code}} {'-F ' + form + ' ' if form else '''}{url}")) == code
+
+
+    def activate_specialisation(name: str):
+        machine.succeed(f"${nodes.machine.config.system.build.toplevel}/specialisation/{name}/bin/switch-to-configuration test >&2")
+
+
+    url = "http://localhost:${toString nodes.machine.config.services.invidious.port}"
+    port = ${toString nodes.machine.config.services.invidious.port}
+
+    # start postgres vm now
+    postgres_tcp.start()
+
+    machine.wait_for_open_port(port)
+    curl_assert_status_code(f"{url}/search", 200)
+
+    activate_specialisation("nginx")
+    machine.wait_for_open_port(80)
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+
+    activate_specialisation("nginx-scale")
+    machine.wait_for_open_port(80)
+    # this depends on nginx round-robin behaviour for the upstream servers
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+    machine.succeed("journalctl -eu invidious.service | grep -o '200 GET /search'")
+    machine.succeed("journalctl -eu invidious-1.service | grep -o '200 GET /search'")
+    machine.succeed("journalctl -eu invidious-2.service | grep -o '200 GET /search'")
+
+    activate_specialisation("nginx-scale-ytproxy")
+    machine.wait_for_unit("http3-ytproxy.service")
+    machine.wait_for_open_port(80)
+    machine.wait_until_succeeds("ls /run/http3-ytproxy/socket/http-proxy.sock")
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+    # this should error out as no internet connectivity is available in the test
+    curl_assert_status_code("http://invidious.example.com/vi/dQw4w9WgXcQ/mqdefault.jpg", 502)
+    machine.succeed("journalctl -eu http3-ytproxy.service | grep -o 'dQw4w9WgXcQ'")
+
+    postgres_tcp.wait_for_unit("postgresql.service")
+    activate_specialisation("postgres-tcp")
+    machine.wait_for_open_port(port)
+    curl_assert_status_code(f"{url}/search", 200)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/invoiceplane.nix b/nixpkgs/nixos/tests/invoiceplane.nix
new file mode 100644
index 000000000000..0b5170717199
--- /dev/null
+++ b/nixpkgs/nixos/tests/invoiceplane.nix
@@ -0,0 +1,106 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "invoiceplane";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [
+      onny
+    ];
+  };
+
+  nodes = {
+    invoiceplane_caddy = { ... }: {
+      services.invoiceplane.webserver = "caddy";
+      services.invoiceplane.sites = {
+        "site1.local" = {
+          database.name = "invoiceplane1";
+          database.createLocally = true;
+          enable = true;
+        };
+        "site2.local" = {
+          database.name = "invoiceplane2";
+          database.createLocally = true;
+          enable = true;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+    };
+
+    invoiceplane_nginx = { ... }: {
+      services.invoiceplane.webserver = "nginx";
+      services.invoiceplane.sites = {
+        "site1.local" = {
+          database.name = "invoiceplane1";
+          database.createLocally = true;
+          enable = true;
+        };
+        "site2.local" = {
+          database.name = "invoiceplane2";
+          database.createLocally = true;
+          enable = true;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    invoiceplane_caddy.wait_for_unit("caddy")
+    invoiceplane_nginx.wait_for_unit("nginx")
+
+    site_names = ["site1.local", "site2.local"]
+
+    machines = [invoiceplane_caddy, invoiceplane_nginx]
+
+    for machine in machines:
+      machine.wait_for_open_port(80)
+      machine.wait_for_open_port(3306)
+
+      for site_name in site_names:
+          machine.wait_for_unit(f"phpfpm-invoiceplane-{site_name}")
+
+          with subtest("Website returns welcome screen"):
+              assert "Please install InvoicePlane" in machine.succeed(f"curl -L {site_name}")
+
+          with subtest("Finish InvoicePlane setup"):
+            machine.succeed(
+              f"curl -sSfL --cookie-jar cjar {site_name}/setup/language"
+            )
+            csrf_token = machine.succeed(
+              "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
+            )
+            machine.succeed(
+              f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&ip_lang=english&btn_continue=Continue' {site_name}/setup/language"
+            )
+            csrf_token = machine.succeed(
+              "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
+            )
+            machine.succeed(
+              f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/setup/prerequisites"
+            )
+            csrf_token = machine.succeed(
+              "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
+            )
+            machine.succeed(
+              f"curl -sSfL --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/setup/configure_database"
+            )
+            csrf_token = machine.succeed(
+              "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
+            )
+            machine.succeed(
+              f"curl -sSfl --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/setup/install_tables"
+            )
+            csrf_token = machine.succeed(
+              "grep ip_csrf_cookie cjar | cut -f 7 | tr -d '\n'"
+            )
+            machine.succeed(
+              f"curl -sSfl --cookie cjar --cookie-jar cjar -d '_ip_csrf={csrf_token}&btn_continue=Continue' {site_name}/setup/upgrade_tables"
+            )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/iodine.nix b/nixpkgs/nixos/tests/iodine.nix
new file mode 100644
index 000000000000..41fb2e7778d4
--- /dev/null
+++ b/nixpkgs/nixos/tests/iodine.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix (
+  { pkgs, ... }: let
+    domain = "whatever.example.com";
+    password = "false;foo;exit;withspecialcharacters";
+  in
+    {
+      name = "iodine";
+      nodes = {
+        server =
+          { ... }:
+
+            {
+              networking.firewall = {
+                allowedUDPPorts = [ 53 ];
+                trustedInterfaces = [ "dns0" ];
+              };
+              boot.kernel.sysctl = {
+                "net.ipv4.ip_forward" = 1;
+                "net.ipv6.ip_forward" = 1;
+              };
+
+              services.iodine.server = {
+                enable = true;
+                ip = "10.53.53.1/24";
+                passwordFile = "${builtins.toFile "password" password}";
+                inherit domain;
+              };
+
+              # test resource: accessible only via tunnel
+              services.openssh = {
+                enable = true;
+                openFirewall = false;
+              };
+            };
+
+        client =
+          { ... }: {
+            services.iodine.clients.testClient = {
+              # test that ProtectHome is "read-only"
+              passwordFile = "/root/pw";
+              relay = "server";
+              server = domain;
+            };
+            systemd.tmpfiles.rules = [
+              "f /root/pw 0666 root root - ${password}"
+            ];
+            environment.systemPackages = [
+              pkgs.nagiosPluginsOfficial
+            ];
+          };
+
+      };
+
+      testScript = ''
+        start_all()
+
+        server.wait_for_unit("sshd")
+        server.wait_for_unit("iodined")
+        client.wait_for_unit("iodine-testClient")
+
+        client.succeed("check_ssh -H 10.53.53.1")
+      '';
+    }
+)
diff --git a/nixpkgs/nixos/tests/ipv6.nix b/nixpkgs/nixos/tests/ipv6.nix
new file mode 100644
index 000000000000..75faa6f60201
--- /dev/null
+++ b/nixpkgs/nixos/tests/ipv6.nix
@@ -0,0 +1,130 @@
+# Test of IPv6 functionality in NixOS, including whether router
+# solicication/advertisement using radvd works.
+
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "ipv6";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes =
+    {
+      # We use lib.mkForce here to remove the interface configuration
+      # provided by makeTest, so that the interfaces are all configured
+      # implicitly.
+
+      # This client should use privacy extensions fully, having a
+      # completely-default network configuration.
+      client_defaults.networking.interfaces = lib.mkForce {};
+
+      # Both of these clients should obtain temporary addresses, but
+      # not use them as the default source IP. We thus run the same
+      # checks against them — but the configuration resulting in this
+      # behaviour is different.
+
+      # Here, by using an altered default value for the global setting...
+      client_global_setting = {
+        networking.interfaces = lib.mkForce {};
+        networking.tempAddresses = "enabled";
+      };
+      # and here, by setting this on the interface explicitly.
+      client_interface_setting = {
+        networking.tempAddresses = "disabled";
+        networking.interfaces = lib.mkForce {
+          eth1.tempAddress = "enabled";
+        };
+      };
+
+      server =
+        { services.httpd.enable = true;
+          services.httpd.adminAddr = "foo@example.org";
+          networking.firewall.allowedTCPPorts = [ 80 ];
+        };
+
+      router =
+        { ... }:
+        { services.radvd.enable = true;
+          services.radvd.config =
+            ''
+              interface eth1 {
+                AdvSendAdvert on;
+                # ULA prefix (RFC 4193).
+                prefix fd60:cc69:b537:1::/64 { };
+              };
+            '';
+        };
+    };
+
+  testScript =
+    ''
+      import re
+
+      # Start the router first so that it respond to router solicitations.
+      router.wait_for_unit("radvd")
+
+      clients = [client_defaults, client_global_setting, client_interface_setting]
+
+      start_all()
+
+      for client in clients:
+          client.wait_for_unit("network.target")
+      server.wait_for_unit("network.target")
+      server.wait_for_unit("httpd.service")
+
+      # Wait until the given interface has a non-tentative address of
+      # the desired scope (i.e. has completed Duplicate Address
+      # Detection).
+      def wait_for_address(machine, iface, scope, temporary=False):
+          temporary_flag = "temporary" if temporary else "-temporary"
+          cmd = f"ip -o -6 addr show dev {iface} scope {scope} -tentative {temporary_flag}"
+
+          machine.wait_until_succeeds(f"[ `{cmd} | wc -l` -eq 1 ]")
+          output = machine.succeed(cmd)
+          ip = re.search(r"inet6 ([0-9a-f:]{2,})/", output).group(1)
+
+          if temporary:
+              scope = scope + " temporary"
+          machine.log(f"{scope} address on {iface} is {ip}")
+          return ip
+
+
+      with subtest("Loopback address can be pinged"):
+          client_defaults.succeed("ping -c 1 ::1 >&2")
+          client_defaults.fail("ping -c 1 2001:db8:: >&2")
+
+      with subtest("Local link addresses can be obtained and pinged"):
+          for client in clients:
+              client_ip = wait_for_address(client, "eth1", "link")
+              server_ip = wait_for_address(server, "eth1", "link")
+              client.succeed(f"ping -c 1 {client_ip}%eth1 >&2")
+              client.succeed(f"ping -c 1 {server_ip}%eth1 >&2")
+
+      with subtest("Global addresses can be obtained, pinged, and reached via http"):
+          for client in clients:
+              client_ip = wait_for_address(client, "eth1", "global")
+              server_ip = wait_for_address(server, "eth1", "global")
+              client.succeed(f"ping -c 1 {client_ip} >&2")
+              client.succeed(f"ping -c 1 {server_ip} >&2")
+              client.succeed(f"curl --fail -g http://[{server_ip}]")
+              client.fail(f"curl --fail -g http://[{client_ip}]")
+
+      with subtest(
+          "Privacy extensions: Global temporary address is used as default source address"
+      ):
+          ip = wait_for_address(client_defaults, "eth1", "global", temporary=True)
+          # Default route should have "src <temporary address>" in it
+          client_defaults.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
+
+      for client, setting_desc in (
+          (client_global_setting, "global"),
+          (client_interface_setting, "interface"),
+      ):
+          with subtest(f'Privacy extensions: "enabled" through {setting_desc} setting)'):
+              # We should be obtaining both a temporary address and an EUI-64 address...
+              ip = wait_for_address(client, "eth1", "global")
+              assert "ff:fe" in ip
+              ip_temp = wait_for_address(client, "eth1", "global", temporary=True)
+              # But using the EUI-64 one.
+              client.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/iscsi-multipath-root.nix b/nixpkgs/nixos/tests/iscsi-multipath-root.nix
new file mode 100644
index 000000000000..494a539b57e0
--- /dev/null
+++ b/nixpkgs/nixos/tests/iscsi-multipath-root.nix
@@ -0,0 +1,267 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  let
+    initiatorName = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
+    targetName = "iqn.2003-01.org.linux-iscsi.target.x8664:sn.acf8fd9c23af";
+  in
+  {
+    name = "iscsi";
+    meta = {
+      maintainers = pkgs.lib.teams.deshaw.members;
+    };
+
+    nodes = {
+      target = { config, pkgs, lib, ... }: {
+        virtualisation.vlans = [ 1 2 ];
+        services.target = {
+          enable = true;
+          config = {
+            fabric_modules = [ ];
+            storage_objects = [
+              {
+                dev = "/dev/vdb";
+                name = "test";
+                plugin = "block";
+                write_back = true;
+                wwn = "92b17c3f-6b40-4168-b082-ceeb7b495522";
+              }
+            ];
+            targets = [
+              {
+                fabric = "iscsi";
+                tpgs = [
+                  {
+                    enable = true;
+                    attributes = {
+                      authentication = 0;
+                      generate_node_acls = 1;
+                    };
+                    luns = [
+                      {
+                        alias = "94dfe06967";
+                        alua_tg_pt_gp_name = "default_tg_pt_gp";
+                        index = 0;
+                        storage_object = "/backstores/block/test";
+                      }
+                    ];
+                    node_acls = [
+                      {
+                        mapped_luns = [
+                          {
+                            alias = "d42f5bdf8a";
+                            index = 0;
+                            tpg_lun = 0;
+                            write_protect = false;
+                          }
+                        ];
+                        node_wwn = initiatorName;
+                      }
+                    ];
+                    portals = [
+                      {
+                        ip_address = "0.0.0.0";
+                        iser = false;
+                        offload = false;
+                        port = 3260;
+                      }
+                    ];
+                    tag = 1;
+                  }
+                ];
+                wwn = targetName;
+              }
+            ];
+          };
+        };
+
+        networking.firewall.allowedTCPPorts = [ 3260 ];
+        networking.firewall.allowedUDPPorts = [ 3260 ];
+
+        virtualisation.memorySize = 2048;
+        virtualisation.emptyDiskImages = [ 2048 ];
+      };
+
+      initiatorAuto = { nodes, config, pkgs, ... }: {
+        virtualisation.vlans = [ 1 2 ];
+
+        services.multipath = {
+          enable = true;
+          defaults = ''
+            find_multipaths yes
+            user_friendly_names yes
+          '';
+          pathGroups = [
+            {
+              alias = 123456;
+              wwid = "3600140592b17c3f6b404168b082ceeb7";
+            }
+          ];
+        };
+
+        services.openiscsi = {
+          enable = true;
+          enableAutoLoginOut = true;
+          discoverPortal = "target";
+          name = initiatorName;
+        };
+
+        environment.systemPackages = with pkgs; [
+          xfsprogs
+        ];
+
+        environment.etc."initiator-root-disk-closure".source = nodes.initiatorRootDisk.config.system.build.toplevel;
+
+        nix.settings = {
+          substituters = lib.mkForce [ ];
+          hashed-mirrors = null;
+          connect-timeout = 1;
+        };
+      };
+
+      initiatorRootDisk = { config, pkgs, modulesPath, lib, ... }: {
+        boot.initrd.network.enable = true;
+        boot.loader.grub.enable = false;
+
+        boot.kernelParams = lib.mkOverride 5 (
+          [
+            "boot.shell_on_fail"
+            "console=tty1"
+            "ip=192.168.1.1:::255.255.255.0::ens9:none"
+            "ip=192.168.2.1:::255.255.255.0::ens10:none"
+          ]
+        );
+
+        # defaults to true, puts some code in the initrd that tries to mount an overlayfs on /nix/store
+        virtualisation.writableStore = false;
+        virtualisation.vlans = [ 1 2 ];
+
+        services.multipath = {
+          enable = true;
+          defaults = ''
+            find_multipaths yes
+            user_friendly_names yes
+          '';
+          pathGroups = [
+            {
+              alias = 123456;
+              wwid = "3600140592b17c3f6b404168b082ceeb7";
+            }
+          ];
+        };
+
+        fileSystems = lib.mkOverride 5 {
+          "/" = {
+            fsType = "xfs";
+            device = "/dev/mapper/123456";
+            options = [ "_netdev" ];
+          };
+        };
+
+        boot.initrd.extraFiles."etc/multipath/wwids".source = pkgs.writeText "wwids" "/3600140592b17c3f6b404168b082ceeb7/";
+
+        boot.iscsi-initiator = {
+          discoverPortal = "target";
+          name = initiatorName;
+          target = targetName;
+          extraIscsiCommands = ''
+            iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login
+          '';
+        };
+      };
+
+    };
+
+    testScript = { nodes, ... }: ''
+      target.start()
+      target.wait_for_unit("iscsi-target.service")
+
+      initiatorAuto.start()
+
+      initiatorAuto.wait_for_unit("iscsid.service")
+      initiatorAuto.wait_for_unit("iscsi.service")
+      initiatorAuto.get_unit_info("iscsi")
+
+      # Expecting this to fail since we should already know about 192.168.1.3
+      initiatorAuto.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.1.3 --login")
+      # Expecting this to succeed since we don't yet know about 192.168.2.3
+      initiatorAuto.succeed("iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login")
+
+      # /dev/sda is provided by iscsi on target
+      initiatorAuto.succeed("set -x; while ! test -e /dev/sda; do sleep 1; done")
+
+      initiatorAuto.succeed("mkfs.xfs /dev/sda")
+      initiatorAuto.succeed("mkdir /mnt")
+
+      # Start by verifying /dev/sda and /dev/sdb are both the same disk
+      initiatorAuto.succeed("mount /dev/sda /mnt")
+      initiatorAuto.succeed("touch /mnt/hi")
+      initiatorAuto.succeed("umount /mnt")
+
+      initiatorAuto.succeed("mount /dev/sdb /mnt")
+      initiatorAuto.succeed("test -e /mnt/hi")
+      initiatorAuto.succeed("umount /mnt")
+
+      initiatorAuto.succeed("systemctl restart multipathd")
+      initiatorAuto.succeed("systemd-cat multipath -ll")
+
+      # Install our RootDisk machine to 123456, the alias to the device that multipath is now managing
+      initiatorAuto.succeed("mount /dev/mapper/123456 /mnt")
+      initiatorAuto.succeed("mkdir -p /mnt/etc/{multipath,iscsi}")
+      initiatorAuto.succeed("cp -r /etc/multipath/wwids /mnt/etc/multipath/wwids")
+      initiatorAuto.succeed("cp -r /etc/iscsi/{nodes,send_targets} /mnt/etc/iscsi")
+      initiatorAuto.succeed(
+        "nixos-install --no-bootloader --no-root-passwd --system /etc/initiator-root-disk-closure"
+      )
+      initiatorAuto.succeed("umount /mnt")
+      initiatorAuto.shutdown()
+
+      initiatorRootDisk.start()
+      initiatorRootDisk.wait_for_unit("multi-user.target")
+      initiatorRootDisk.wait_for_unit("iscsid")
+
+      # Log in over both nodes
+      initiatorRootDisk.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.1.3 --login")
+      initiatorRootDisk.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login")
+      initiatorRootDisk.succeed("systemctl restart multipathd")
+      initiatorRootDisk.succeed("systemd-cat multipath -ll")
+
+      # Verify we can write and sync the root disk
+      initiatorRootDisk.succeed("mkdir /scratch")
+      initiatorRootDisk.succeed("touch /scratch/both-up")
+      initiatorRootDisk.succeed("sync /scratch")
+
+      # Verify we can write to the root with ens9 (sda, 192.168.1.3) down
+      initiatorRootDisk.succeed("ip link set ens9 down")
+      initiatorRootDisk.succeed("touch /scratch/ens9-down")
+      initiatorRootDisk.succeed("sync /scratch")
+      initiatorRootDisk.succeed("ip link set ens9 up")
+
+      # todo: better way to wait until multipath notices the link is back
+      initiatorRootDisk.succeed("sleep 5")
+      initiatorRootDisk.succeed("touch /scratch/both-down")
+      initiatorRootDisk.succeed("sync /scratch")
+
+      # Verify we can write to the root with ens10 (sdb, 192.168.2.3) down
+      initiatorRootDisk.succeed("ip link set ens10 down")
+      initiatorRootDisk.succeed("touch /scratch/ens10-down")
+      initiatorRootDisk.succeed("sync /scratch")
+      initiatorRootDisk.succeed("ip link set ens10 up")
+      initiatorRootDisk.succeed("touch /scratch/ens10-down")
+      initiatorRootDisk.succeed("sync /scratch")
+
+      initiatorRootDisk.succeed("ip link set ens9 up")
+      initiatorRootDisk.succeed("ip link set ens10 up")
+      initiatorRootDisk.shutdown()
+
+      # Verify we can boot with the target's eth1 down, forcing
+      # it to multipath via the second link
+      target.succeed("ip link set eth1 down")
+      initiatorRootDisk.start()
+      initiatorRootDisk.wait_for_unit("multi-user.target")
+      initiatorRootDisk.wait_for_unit("iscsid")
+      initiatorRootDisk.succeed("test -e /scratch/both-up")
+    '';
+  }
+)
+
+
diff --git a/nixpkgs/nixos/tests/iscsi-root.nix b/nixpkgs/nixos/tests/iscsi-root.nix
new file mode 100644
index 000000000000..0d7c48464eec
--- /dev/null
+++ b/nixpkgs/nixos/tests/iscsi-root.nix
@@ -0,0 +1,161 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+    let
+      initiatorName = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
+      targetName = "iqn.2003-01.org.linux-iscsi.target.x8664:sn.acf8fd9c23af";
+    in
+      {
+        name = "iscsi";
+        meta = {
+          maintainers = lib.teams.deshaw.members
+            ++ lib.teams.helsinki-systems.members;
+        };
+
+        nodes = {
+          target = { config, pkgs, lib, ... }: {
+            services.target = {
+              enable = true;
+              config = {
+                fabric_modules = [];
+                storage_objects = [
+                  {
+                    dev = "/dev/vdb";
+                    name = "test";
+                    plugin = "block";
+                    write_back = true;
+                    wwn = "92b17c3f-6b40-4168-b082-ceeb7b495522";
+                  }
+                ];
+                targets = [
+                  {
+                    fabric = "iscsi";
+                    tpgs = [
+                      {
+                        enable = true;
+                        attributes = {
+                          authentication = 0;
+                          generate_node_acls = 1;
+                        };
+                        luns = [
+                          {
+                            alias = "94dfe06967";
+                            alua_tg_pt_gp_name = "default_tg_pt_gp";
+                            index = 0;
+                            storage_object = "/backstores/block/test";
+                          }
+                        ];
+                        node_acls = [
+                          {
+                            mapped_luns = [
+                              {
+                                alias = "d42f5bdf8a";
+                                index = 0;
+                                tpg_lun = 0;
+                                write_protect = false;
+                              }
+                            ];
+                            node_wwn = initiatorName;
+                          }
+                        ];
+                        portals = [
+                          {
+                            ip_address = "0.0.0.0";
+                            iser = false;
+                            offload = false;
+                            port = 3260;
+                          }
+                        ];
+                        tag = 1;
+                      }
+                    ];
+                    wwn = targetName;
+                  }
+                ];
+              };
+            };
+
+            networking.firewall.allowedTCPPorts = [ 3260 ];
+            networking.firewall.allowedUDPPorts = [ 3260 ];
+
+            virtualisation.memorySize = 2048;
+            virtualisation.emptyDiskImages = [ 2048 ];
+          };
+
+          initiatorAuto = { nodes, config, pkgs, ... }: {
+            services.openiscsi = {
+              enable = true;
+              enableAutoLoginOut = true;
+              discoverPortal = "target";
+              name = initiatorName;
+            };
+
+            environment.systemPackages = with pkgs; [
+              xfsprogs
+            ];
+
+            system.extraDependencies = [ nodes.initiatorRootDisk.config.system.build.toplevel ];
+
+            nix.settings = {
+              substituters = lib.mkForce [];
+              hashed-mirrors = null;
+              connect-timeout = 1;
+            };
+          };
+
+          initiatorRootDisk = { config, pkgs, modulesPath, lib, ... }: {
+            boot.loader.grub.enable = false;
+            boot.kernelParams = lib.mkOverride 5 (
+              [
+                "boot.shell_on_fail"
+                "console=tty1"
+                "ip=${config.networking.primaryIPAddress}:::255.255.255.0::ens9:none"
+              ]
+            );
+
+            # defaults to true, puts some code in the initrd that tries to mount an overlayfs on /nix/store
+            virtualisation.writableStore = false;
+
+            fileSystems = lib.mkOverride 5 {
+              "/" = {
+                fsType = "xfs";
+                device = "/dev/sda";
+                options = [ "_netdev" ];
+              };
+            };
+
+            boot.iscsi-initiator = {
+              discoverPortal = "target";
+              name = initiatorName;
+              target = targetName;
+            };
+          };
+        };
+
+        testScript = { nodes, ... }: ''
+          target.start()
+          target.wait_for_unit("iscsi-target.service")
+
+          initiatorAuto.start()
+
+          initiatorAuto.wait_for_unit("iscsid.service")
+          initiatorAuto.wait_for_unit("iscsi.service")
+          initiatorAuto.get_unit_info("iscsi")
+
+          initiatorAuto.succeed("set -x; while ! test -e /dev/sda; do sleep 1; done")
+
+          initiatorAuto.succeed("mkfs.xfs /dev/sda")
+          initiatorAuto.succeed("mkdir /mnt && mount /dev/sda /mnt")
+          initiatorAuto.succeed(
+              "nixos-install --no-bootloader --no-root-passwd --system ${nodes.initiatorRootDisk.config.system.build.toplevel}"
+          )
+          initiatorAuto.succeed("umount /mnt && rmdir /mnt")
+          initiatorAuto.shutdown()
+
+          initiatorRootDisk.start()
+          initiatorRootDisk.wait_for_unit("multi-user.target")
+          initiatorRootDisk.wait_for_unit("iscsid")
+          initiatorRootDisk.succeed("touch test")
+          initiatorRootDisk.shutdown()
+        '';
+      }
+)
diff --git a/nixpkgs/nixos/tests/isso.nix b/nixpkgs/nixos/tests/isso.nix
new file mode 100644
index 000000000000..4ec8b5ec3593
--- /dev/null
+++ b/nixpkgs/nixos/tests/isso.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "isso";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.isso = {
+      enable = true;
+      settings = {
+        general = {
+          dbpath = "/var/lib/isso/comments.db";
+          host = "http://localhost";
+        };
+      };
+    };
+  };
+
+  testScript = let
+    port = 8080;
+  in
+  ''
+    machine.wait_for_unit("isso.service")
+
+    machine.wait_for_open_port(${toString port})
+
+    machine.succeed("curl --fail http://localhost:${toString port}/?uri")
+    machine.succeed("curl --fail http://localhost:${toString port}/js/embed.min.js")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/jackett.nix b/nixpkgs/nixos/tests/jackett.nix
new file mode 100644
index 000000000000..bc8b724e8b4b
--- /dev/null
+++ b/nixpkgs/nixos/tests/jackett.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "jackett";
+  meta.maintainers = with lib.maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.jackett.enable = true; };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("jackett.service")
+    machine.wait_for_open_port(9117)
+    machine.succeed("curl --fail http://localhost:9117/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/jellyfin.nix b/nixpkgs/nixos/tests/jellyfin.nix
new file mode 100644
index 000000000000..7d3097b58629
--- /dev/null
+++ b/nixpkgs/nixos/tests/jellyfin.nix
@@ -0,0 +1,155 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+  {
+    name = "jellyfin";
+    meta.maintainers = with lib.maintainers; [ minijackson ];
+
+    nodes.machine =
+      { ... }:
+      {
+        services.jellyfin.enable = true;
+        environment.systemPackages = with pkgs; [ ffmpeg ];
+      };
+
+    # Documentation of the Jellyfin API: https://api.jellyfin.org/
+    # Beware, this link can be resource intensive
+    testScript =
+      let
+        payloads = {
+          auth = pkgs.writeText "auth.json" (builtins.toJSON {
+            Username = "jellyfin";
+          });
+          empty = pkgs.writeText "empty.json" (builtins.toJSON { });
+        };
+      in
+      ''
+        import json
+        from urllib.parse import urlencode
+
+        machine.wait_for_unit("jellyfin.service")
+        machine.wait_for_open_port(8096)
+        machine.succeed("curl --fail http://localhost:8096/")
+
+        machine.wait_until_succeeds("curl --fail http://localhost:8096/health | grep Healthy")
+
+        auth_header = 'MediaBrowser Client="NixOS Integration Tests", DeviceId="1337", Device="Apple II", Version="20.09"'
+
+
+        def api_get(path):
+            return f"curl --fail 'http://localhost:8096{path}' -H 'X-Emby-Authorization:{auth_header}'"
+
+
+        def api_post(path, json_file=None):
+            if json_file:
+                return f"curl --fail -X post 'http://localhost:8096{path}' -d '@{json_file}' -H Content-Type:application/json -H 'X-Emby-Authorization:{auth_header}'"
+            else:
+                return f"curl --fail -X post 'http://localhost:8096{path}' -H 'X-Emby-Authorization:{auth_header}'"
+
+
+        with machine.nested("Wizard completes"):
+            machine.wait_until_succeeds(api_get("/Startup/Configuration"))
+            machine.succeed(api_get("/Startup/FirstUser"))
+            machine.succeed(api_post("/Startup/Complete"))
+
+        with machine.nested("Can login"):
+            auth_result_str = machine.succeed(
+                api_post(
+                    "/Users/AuthenticateByName",
+                    "${payloads.auth}",
+                )
+            )
+            auth_result = json.loads(auth_result_str)
+            auth_token = auth_result["AccessToken"]
+            auth_header += f", Token={auth_token}"
+
+            sessions_result_str = machine.succeed(api_get("/Sessions"))
+            sessions_result = json.loads(sessions_result_str)
+
+            this_session = [
+                session for session in sessions_result if session["DeviceId"] == "1337"
+            ]
+            if len(this_session) != 1:
+                raise Exception("Session not created")
+
+            me_str = machine.succeed(api_get("/Users/Me"))
+            me = json.loads(me_str)["Id"]
+
+        with machine.nested("Can add library"):
+            tempdir = machine.succeed("mktemp -d -p /var/lib/jellyfin").strip()
+            machine.succeed(f"chmod 755 '{tempdir}'")
+
+            # Generate a dummy video that we can test later
+            videofile = f"{tempdir}/Big Buck Bunny (2008) [1080p].mkv"
+            machine.succeed(f"ffmpeg -f lavfi -i testsrc2=duration=5 '{videofile}'")
+
+            add_folder_query = urlencode(
+                {
+                    "name": "My Library",
+                    "collectionType": "Movies",
+                    "paths": tempdir,
+                    "refreshLibrary": "true",
+                }
+            )
+
+            machine.succeed(
+                api_post(
+                    f"/Library/VirtualFolders?{add_folder_query}",
+                    "${payloads.empty}",
+                )
+            )
+
+
+        def is_refreshed(_):
+            folders_str = machine.succeed(api_get("/Library/VirtualFolders"))
+            folders = json.loads(folders_str)
+            print(folders)
+            return all(folder["RefreshStatus"] == "Idle" for folder in folders)
+
+
+        retry(is_refreshed)
+
+        with machine.nested("Can identify videos"):
+            items = []
+
+            # For some reason, having the folder refreshed doesn't mean the
+            # movie was scanned
+            def has_movie(_):
+                global items
+
+                items_str = machine.succeed(
+                    api_get(f"/Users/{me}/Items?IncludeItemTypes=Movie&Recursive=true")
+                )
+                items = json.loads(items_str)["Items"]
+
+                return len(items) == 1
+
+            retry(has_movie)
+
+            video = items[0]["Id"]
+
+            item_info_str = machine.succeed(api_get(f"/Users/{me}/Items/{video}"))
+            item_info = json.loads(item_info_str)
+
+            if item_info["Name"] != "Big Buck Bunny":
+                raise Exception("Jellyfin failed to properly identify file")
+
+        with machine.nested("Can read videos"):
+            media_source_id = item_info["MediaSources"][0]["Id"]
+
+            machine.succeed(
+                "ffmpeg"
+                + f" -headers 'X-Emby-Authorization:{auth_header}'"
+                + f" -i http://localhost:8096/Videos/{video}/master.m3u8?mediaSourceId={media_source_id}"
+                + " /tmp/test.mkv"
+            )
+
+            duration = machine.succeed(
+                "ffprobe /tmp/test.mkv"
+                + " -show_entries format=duration"
+                + " -of compact=print_section=0:nokey=1"
+            )
+
+            if duration.strip() != "5.000000":
+                raise Exception("Downloaded video has wrong duration")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/jenkins-cli.nix b/nixpkgs/nixos/tests/jenkins-cli.nix
new file mode 100644
index 000000000000..f25e1604da33
--- /dev/null
+++ b/nixpkgs/nixos/tests/jenkins-cli.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, ...} : rec {
+  name = "jenkins-cli";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ pamplemousse ];
+  };
+
+  nodes = {
+    machine =
+      { ... }:
+      {
+        services.jenkins = {
+          enable = true;
+          withCLI = true;
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("jenkins")
+
+    assert "JENKINS_URL" in machine.succeed("env")
+    assert "http://0.0.0.0:8080" in machine.succeed("echo $JENKINS_URL")
+
+    machine.succeed(
+        "jenkins-cli -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword)"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/jenkins.nix b/nixpkgs/nixos/tests/jenkins.nix
new file mode 100644
index 000000000000..a8f621000654
--- /dev/null
+++ b/nixpkgs/nixos/tests/jenkins.nix
@@ -0,0 +1,123 @@
+# verifies:
+#   1. jenkins service starts on master node
+#   2. jenkins user can be extended on both master and slave
+#   3. jenkins service not started on slave node
+#   4. declarative jobs can be added and removed
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "jenkins";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ bjornfor coconnor domenkozar eelco ];
+  };
+
+  nodes = {
+
+    master =
+      { ... }:
+      { services.jenkins = {
+          enable = true;
+          jobBuilder = {
+            enable = true;
+            nixJobs = [
+              { job = {
+                  name = "job-1";
+                  builders = [
+                    { shell = ''
+                        echo "Running job-1"
+                      '';
+                    }
+                  ];
+                };
+              }
+
+              { job = {
+                  name = "folder-1";
+                  project-type = "folder";
+                };
+              }
+
+              { job = {
+                  name = "folder-1/job-2";
+                  builders = [
+                    { shell = ''
+                        echo "Running job-2"
+                      '';
+                    }
+                  ];
+                };
+              }
+            ];
+          };
+        };
+
+        specialisation.noJenkinsJobs.configuration = {
+          services.jenkins.jobBuilder.nixJobs = pkgs.lib.mkForce [];
+        };
+
+        # should have no effect
+        services.jenkinsSlave.enable = true;
+
+        users.users.jenkins.extraGroups = [ "users" ];
+
+        systemd.services.jenkins.serviceConfig.TimeoutStartSec = "6min";
+      };
+
+    slave =
+      { ... }:
+      { services.jenkinsSlave.enable = true;
+
+        users.users.jenkins.extraGroups = [ "users" ];
+      };
+
+  };
+
+  testScript = { nodes, ... }:
+    let
+      configWithoutJobs = "${nodes.master.system.build.toplevel}/specialisation/noJenkinsJobs";
+      jenkinsPort = nodes.master.services.jenkins.port;
+      jenkinsUrl = "http://localhost:${toString jenkinsPort}";
+    in ''
+    start_all()
+
+    master.wait_for_unit("default.target")
+
+    assert "Authentication required" in master.succeed("curl http://localhost:8080")
+
+    for host in master, slave:
+        groups = host.succeed("sudo -u jenkins groups")
+        assert "jenkins" in groups
+        assert "users" in groups
+
+    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_until_succeeds("test -f /var/lib/jenkins/jobs/job-1/config.xml")
+        master.wait_until_succeeds("test -f /var/lib/jenkins/jobs/folder-1/config.xml")
+        master.wait_until_succeeds("test -f /var/lib/jenkins/jobs/folder-1/jobs/job-2/config.xml")
+
+        # Verify that jenkins also sees the jobs.
+        out = master.succeed("${pkgs.jenkins}/bin/jenkins-cli -s ${jenkinsUrl} -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword) list-jobs")
+        jobs = [x.strip() for x in out.splitlines()]
+        # Seeing jobs inside folders requires the Folders plugin
+        # (https://plugins.jenkins.io/cloudbees-folder/), which we don't have
+        # in this vanilla jenkins install, so limit ourself to non-folder jobs.
+        assert jobs == ['job-1'], f"jobs != ['job-1']: {jobs}"
+
+        master.succeed(
+            "${configWithoutJobs}/bin/switch-to-configuration test >&2"
+        )
+
+        # Check that jobs are removed from disk.
+        master.wait_until_fails("test -f /var/lib/jenkins/jobs/job-1/config.xml")
+        master.wait_until_fails("test -f /var/lib/jenkins/jobs/folder-1/config.xml")
+        master.wait_until_fails("test -f /var/lib/jenkins/jobs/folder-1/jobs/job-2/config.xml")
+
+        # Verify that jenkins also sees the jobs as removed.
+        out = master.succeed("${pkgs.jenkins}/bin/jenkins-cli -s ${jenkinsUrl} -auth admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword) list-jobs")
+        jobs = [x.strip() for x in out.splitlines()]
+        assert jobs == [], f"jobs != []: {jobs}"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/jibri.nix b/nixpkgs/nixos/tests/jibri.nix
new file mode 100644
index 000000000000..45e30af9a9a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/jibri.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "jibri";
+  meta = with pkgs.lib; {
+    maintainers = teams.jitsi.members;
+  };
+
+    nodes.machine = { config, pkgs, ... }: {
+      virtualisation.memorySize = 5120;
+
+      services.jitsi-meet = {
+        enable = true;
+        hostName = "machine";
+        jibri.enable = true;
+      };
+      services.jibri.ignoreCert = true;
+      services.jitsi-videobridge.openFirewall = true;
+
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      services.nginx.virtualHosts.machine = {
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      security.acme.email = "me@example.org";
+      security.acme.acceptTerms = true;
+      security.acme.server = "https://example.com"; # self-signed only
+    };
+
+  testScript = ''
+    machine.wait_for_unit("jitsi-videobridge2.service")
+    machine.wait_for_unit("jicofo.service")
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_unit("prosody.service")
+    machine.wait_for_unit("jibri.service")
+
+    machine.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.machine'", timeout=31
+    )
+    machine.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jvb@auth.machine'", timeout=32
+    )
+    machine.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jibri@auth.machine'", timeout=33
+    )
+    machine.wait_until_succeeds(
+        "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Joined MUC: jibribrewery@internal.machine'", timeout=34
+    )
+
+    assert '"busyStatus":"IDLE","health":{"healthStatus":"HEALTHY"' in machine.succeed(
+        "curl -X GET http://machine:2222/jibri/api/v1.0/health"
+    )
+    machine.succeed(
+        """curl -H "Content-Type: application/json" -X POST http://localhost:2222/jibri/api/v1.0/startService -d '{"sessionId": "RecordTest","callParams":{"callUrlInfo":{"baseUrl": "https://machine","callName": "TestCall"}},"callLoginParams":{"domain": "recorder.machine", "username": "recorder", "password": "'"$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"'" },"sinkType": "file"}'"""
+    )
+    machine.wait_until_succeeds(
+        "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'File recording service transitioning from state Starting up to Running'", timeout=35
+    )
+    machine.succeed(
+        """sleep 15 && curl -H "Content-Type: application/json" -X POST http://localhost:2222/jibri/api/v1.0/stopService -d '{"sessionId": "RecordTest","callParams":{"callUrlInfo":{"baseUrl": "https://machine","callName": "TestCall"}},"callLoginParams":{"domain": "recorder.machine", "username": "recorder", "password": "'"$(cat /var/lib/jitsi-meet/jibri-recorder-secret)"'" },"sinkType": "file"}'"""
+    )
+    machine.wait_until_succeeds(
+        "cat /var/log/jitsi/jibri/log.0.txt | grep -q 'Finalize script finished with exit value 0'", timeout=36
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/jirafeau.nix b/nixpkgs/nixos/tests/jirafeau.nix
new file mode 100644
index 000000000000..dbfaf515e257
--- /dev/null
+++ b/nixpkgs/nixos/tests/jirafeau.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "jirafeau";
+  meta.maintainers = with lib.maintainers; [ davidtwco ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.jirafeau = {
+      enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("phpfpm-jirafeau.service")
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_open_port(80)
+    machine.succeed("curl -sSfL http://localhost/ | grep 'Jirafeau'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/jitsi-meet.nix b/nixpkgs/nixos/tests/jitsi-meet.nix
new file mode 100644
index 000000000000..fc6654f2c076
--- /dev/null
+++ b/nixpkgs/nixos/tests/jitsi-meet.nix
@@ -0,0 +1,68 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "jitsi-meet";
+  meta = with pkgs.lib; {
+    maintainers = teams.jitsi.members;
+  };
+
+  nodes = {
+    client = { nodes, pkgs, ... }: {
+    };
+    server = { config, pkgs, ... }: {
+      services.jitsi-meet = {
+        enable = true;
+        hostName = "server";
+      };
+      services.jitsi-videobridge.openFirewall = true;
+
+      networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+      services.nginx.virtualHosts.server = {
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      security.acme.acceptTerms = true;
+      security.acme.defaults.email = "me@example.org";
+      security.acme.defaults.server = "https://example.com"; # self-signed only
+
+      specialisation.caddy = {
+        inheritParentConfig = true;
+        configuration = {
+          services.jitsi-meet = {
+            caddy.enable = true;
+            nginx.enable = false;
+          };
+          services.caddy.virtualHosts.${config.services.jitsi-meet.hostName}.extraConfig = ''
+            tls internal
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    server.wait_for_unit("jitsi-videobridge2.service")
+    server.wait_for_unit("jicofo.service")
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("prosody.service")
+
+    server.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as focus@auth.server'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -b -u prosody -o cat | grep -q 'Authenticated as jvb@auth.server'"
+    )
+
+    client.wait_for_unit("network.target")
+
+    def client_curl():
+        assert "<title>Jitsi Meet</title>" in client.succeed("curl -sSfkL http://server/")
+
+    client_curl()
+
+    with subtest("Testing backup service"):
+        server.succeed("${nodes.server.system.build.toplevel}/specialisation/caddy/bin/switch-to-configuration test")
+        server.wait_for_unit("caddy.service")
+        client_curl()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/jool.nix b/nixpkgs/nixos/tests/jool.nix
new file mode 100644
index 000000000000..93575f07b1c8
--- /dev/null
+++ b/nixpkgs/nixos/tests/jool.nix
@@ -0,0 +1,220 @@
+{ pkgs, runTest }:
+
+let
+  inherit (pkgs) lib;
+
+  ipv6Only = {
+    networking.useDHCP = false;
+    networking.interfaces.eth1.ipv4.addresses = lib.mkVMOverride [ ];
+  };
+
+  ipv4Only = {
+    networking.useDHCP = false;
+    networking.interfaces.eth1.ipv6.addresses = lib.mkVMOverride [ ];
+  };
+
+  webserver = ip: msg: {
+    systemd.services.webserver = {
+      description = "Mock webserver";
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        while true; do
+        {
+          printf 'HTTP/1.0 200 OK\n'
+          printf 'Content-Length: ${toString (1 + builtins.stringLength msg)}\n'
+          printf '\n${msg}\n\n'
+        } | ${pkgs.libressl.nc}/bin/nc -${toString ip}nvl 80
+        done
+      '';
+    };
+    networking.firewall.allowedTCPPorts = [ 80 ];
+  };
+
+in
+
+{
+  siit = runTest {
+    # This test simulates the setup described in [1] with two IPv6 and
+    # IPv4-only devices on different subnets communicating through a border
+    # relay running Jool in SIIT mode.
+    # [1]: https://nicmx.github.io/Jool/en/run-vanilla.html
+    name = "jool-siit";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+    # Border relay
+    nodes.relay = {
+      virtualisation.vlans = [ 1 2 ];
+
+      # Enable packet routing
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.all.forwarding" = 1;
+        "net.ipv4.conf.all.forwarding" = 1;
+      };
+
+      networking.useDHCP = false;
+      networking.interfaces = lib.mkVMOverride {
+        eth1.ipv6.addresses = [ { address = "fd::198.51.100.1"; prefixLength = 120; } ];
+        eth2.ipv4.addresses = [ { address = "192.0.2.1";  prefixLength = 24; } ];
+      };
+
+      networking.jool.enable = true;
+      networking.jool.siit.default.global.pool6 = "fd::/96";
+    };
+
+    # IPv6 only node
+    nodes.alice = {
+      imports = [ ipv6Only (webserver 6 "Hello, Bob!") ];
+
+      virtualisation.vlans = [ 1 ];
+      networking.interfaces.eth1.ipv6 = {
+        addresses = [ { address = "fd::198.51.100.8"; prefixLength = 120; } ];
+        routes    = [ { address = "fd::192.0.2.0"; prefixLength = 120;
+                        via = "fd::198.51.100.1"; } ];
+      };
+    };
+
+    # IPv4 only node
+    nodes.bob = {
+      imports = [ ipv4Only (webserver 4 "Hello, Alice!") ];
+
+      virtualisation.vlans = [ 2 ];
+      networking.interfaces.eth1.ipv4 = {
+        addresses = [ { address = "192.0.2.16"; prefixLength = 24; } ];
+        routes    = [ { address = "198.51.100.0"; prefixLength = 24;
+                        via = "192.0.2.1"; } ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      relay.wait_for_unit("jool-siit-default.service")
+      alice.wait_for_unit("network-addresses-eth1.service")
+      bob.wait_for_unit("network-addresses-eth1.service")
+
+      with subtest("Alice and Bob can't ping each other"):
+        relay.systemctl("stop jool-siit-default.service")
+        alice.fail("ping -c1 fd::192.0.2.16")
+        bob.fail("ping -c1 198.51.100.8")
+
+      with subtest("Alice and Bob can ping using the relay"):
+        relay.systemctl("start jool-siit-default.service")
+        alice.wait_until_succeeds("ping -c1 fd::192.0.2.16")
+        bob.wait_until_succeeds("ping -c1 198.51.100.8")
+
+      with subtest("Alice can connect to Bob's webserver"):
+        bob.wait_for_open_port(80)
+        alice.succeed("curl -vvv http://[fd::192.0.2.16] >&2")
+        alice.succeed("curl --fail -s http://[fd::192.0.2.16] | grep -q Alice")
+
+      with subtest("Bob can connect to Alices's webserver"):
+        alice.wait_for_open_port(80)
+        bob.succeed("curl --fail -s http://198.51.100.8 | grep -q Bob")
+    '';
+  };
+
+  nat64 = runTest {
+    # This test simulates the setup described in [1] with two IPv6-only nodes
+    # (a client and a homeserver) on the LAN subnet and an IPv4 node on the WAN.
+    # The router runs Jool in stateful NAT64 mode, masquarading the LAN and
+    # forwarding ports using static BIB entries.
+    # [1]: https://nicmx.github.io/Jool/en/run-nat64.html
+    name = "jool-nat64";
+    meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+    # Router
+    nodes.router = {
+      virtualisation.vlans = [ 1 2 ];
+
+      # Enable packet routing
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.all.forwarding" = 1;
+        "net.ipv4.conf.all.forwarding" = 1;
+      };
+
+      networking.useDHCP = false;
+      networking.interfaces = lib.mkVMOverride {
+        eth1.ipv6.addresses = [ { address = "2001:db8::1"; prefixLength = 96; } ];
+        eth2.ipv4.addresses = [ { address = "203.0.113.1"; prefixLength = 24; } ];
+      };
+
+      networking.jool.enable = true;
+      networking.jool.nat64.default = {
+        bib = [
+          { # forward HTTP 203.0.113.1 (router) → 2001:db8::9 (homeserver)
+            "protocol"     = "TCP";
+            "ipv4 address" = "203.0.113.1#80";
+            "ipv6 address" = "2001:db8::9#80";
+          }
+        ];
+        pool4 = [
+          # Ports for dynamic translation
+          { protocol =  "TCP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+          { protocol =  "UDP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+          { protocol = "ICMP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+          # Ports for static BIB entries
+          { protocol =  "TCP";  prefix = "203.0.113.1/32"; "port range" = "80"; }
+        ];
+      };
+    };
+
+    # LAN client (IPv6 only)
+    nodes.client = {
+      imports = [ ipv6Only ];
+      virtualisation.vlans = [ 1 ];
+
+      networking.interfaces.eth1.ipv6 = {
+        addresses = [ { address = "2001:db8::8"; prefixLength = 96; } ];
+        routes    = [ { address = "64:ff9b::";   prefixLength = 96;
+                        via = "2001:db8::1"; } ];
+      };
+    };
+
+    # LAN server (IPv6 only)
+    nodes.homeserver = {
+      imports = [ ipv6Only (webserver 6 "Hello from IPv6!") ];
+
+      virtualisation.vlans = [ 1 ];
+      networking.interfaces.eth1.ipv6 = {
+        addresses = [ { address = "2001:db8::9"; prefixLength = 96; } ];
+        routes    = [ { address = "64:ff9b::";   prefixLength = 96;
+                        via = "2001:db8::1"; } ];
+      };
+    };
+
+    # WAN server (IPv4 only)
+    nodes.server = {
+      imports = [ ipv4Only (webserver 4 "Hello from IPv4!") ];
+
+      virtualisation.vlans = [ 2 ];
+      networking.interfaces.eth1.ipv4.addresses =
+        [ { address = "203.0.113.16"; prefixLength = 24; } ];
+    };
+
+    testScript = ''
+      start_all()
+
+      for node in [client, homeserver, server]:
+        node.wait_for_unit("network-addresses-eth1.service")
+
+      with subtest("Client can ping the WAN server"):
+        router.wait_for_unit("jool-nat64-default.service")
+        client.succeed("ping -c1 64:ff9b::203.0.113.16")
+
+      with subtest("Client can connect to the WAN webserver"):
+        server.wait_for_open_port(80)
+        client.succeed("curl --fail -s http://[64:ff9b::203.0.113.16] | grep -q IPv4!")
+
+      with subtest("Router BIB entries are correctly populated"):
+        router.succeed("jool bib display | grep -q 'Dynamic TCP.*2001:db8::8'")
+        router.succeed("jool bib display | grep -q 'Static TCP.*2001:db8::9'")
+
+      with subtest("WAN server can reach the LAN server"):
+        homeserver.wait_for_open_port(80)
+        server.succeed("curl --fail -s http://203.0.113.1 | grep -q IPv6!")
+    '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/tests/k3s/default.nix b/nixpkgs/nixos/tests/k3s/default.nix
new file mode 100644
index 000000000000..e168f8233c76
--- /dev/null
+++ b/nixpkgs/nixos/tests/k3s/default.nix
@@ -0,0 +1,13 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+, lib ? pkgs.lib
+}:
+let
+  allK3s = lib.filterAttrs (n: _: lib.strings.hasPrefix "k3s_" n) pkgs;
+in
+{
+  # Run a single node k3s cluster and verify a pod can run
+  single-node = lib.mapAttrs (_: k3s: import ./single-node.nix { inherit system pkgs k3s; }) allK3s;
+  # Run a multi-node k3s cluster and verify pod networking works across nodes
+  multi-node = lib.mapAttrs (_: k3s: import ./multi-node.nix { inherit system pkgs k3s; }) allK3s;
+}
diff --git a/nixpkgs/nixos/tests/k3s/multi-node.nix b/nixpkgs/nixos/tests/k3s/multi-node.nix
new file mode 100644
index 000000000000..932b4639b39c
--- /dev/null
+++ b/nixpkgs/nixos/tests/k3s/multi-node.nix
@@ -0,0 +1,183 @@
+import ../make-test-python.nix ({ pkgs, lib, k3s, ... }:
+  let
+    imageEnv = pkgs.buildEnv {
+      name = "k3s-pause-image-env";
+      paths = with pkgs; [ tini bashInteractive coreutils socat ];
+    };
+    pauseImage = pkgs.dockerTools.streamLayeredImage {
+      name = "test.local/pause";
+      tag = "local";
+      contents = imageEnv;
+      config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ];
+    };
+    # A daemonset that responds 'server' on port 8000
+    networkTestDaemonset = pkgs.writeText "test.yml" ''
+      apiVersion: apps/v1
+      kind: DaemonSet
+      metadata:
+        name: test
+        labels:
+          name: test
+      spec:
+        selector:
+          matchLabels:
+            name: test
+        template:
+          metadata:
+            labels:
+              name: test
+          spec:
+            containers:
+            - name: test
+              image: test.local/pause:local
+              imagePullPolicy: Never
+              resources:
+                limits:
+                  memory: 20Mi
+              command: ["socat", "TCP4-LISTEN:8000,fork", "EXEC:echo server"]
+    '';
+    tokenFile = pkgs.writeText "token" "p@s$w0rd";
+  in
+  {
+    name = "${k3s.name}-multi-node";
+
+    nodes = {
+      server = { pkgs, ... }: {
+        environment.systemPackages = with pkgs; [ gzip jq ];
+        # k3s uses enough resources the default vm fails.
+        virtualisation.memorySize = 1536;
+        virtualisation.diskSize = 4096;
+
+        services.k3s = {
+          inherit tokenFile;
+          enable = true;
+          role = "server";
+          package = k3s;
+          clusterInit = true;
+          extraFlags = builtins.toString [
+            "--disable" "coredns"
+            "--disable" "local-storage"
+            "--disable" "metrics-server"
+            "--disable" "servicelb"
+            "--disable" "traefik"
+            "--node-ip" "192.168.1.1"
+            "--pause-image" "test.local/pause:local"
+          ];
+        };
+        networking.firewall.allowedTCPPorts = [ 2379 2380 6443 ];
+        networking.firewall.allowedUDPPorts = [ 8472 ];
+        networking.firewall.trustedInterfaces = [ "flannel.1" ];
+        networking.useDHCP = false;
+        networking.defaultGateway = "192.168.1.1";
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+
+      server2 = { pkgs, ... }: {
+        environment.systemPackages = with pkgs; [ gzip jq ];
+        virtualisation.memorySize = 1536;
+        virtualisation.diskSize = 4096;
+
+        services.k3s = {
+          inherit tokenFile;
+          enable = true;
+          serverAddr = "https://192.168.1.1:6443";
+          clusterInit = false;
+          extraFlags = builtins.toString [
+            "--disable" "coredns"
+            "--disable" "local-storage"
+            "--disable" "metrics-server"
+            "--disable" "servicelb"
+            "--disable" "traefik"
+            "--node-ip" "192.168.1.3"
+            "--pause-image" "test.local/pause:local"
+          ];
+        };
+        networking.firewall.allowedTCPPorts = [ 2379 2380 6443 ];
+        networking.firewall.allowedUDPPorts = [ 8472 ];
+        networking.firewall.trustedInterfaces = [ "flannel.1" ];
+        networking.useDHCP = false;
+        networking.defaultGateway = "192.168.1.3";
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.3"; prefixLength = 24; }
+        ];
+      };
+
+      agent = { pkgs, ... }: {
+        virtualisation.memorySize = 1024;
+        virtualisation.diskSize = 2048;
+        services.k3s = {
+          inherit tokenFile;
+          enable = true;
+          role = "agent";
+          serverAddr = "https://192.168.1.3:6443";
+          extraFlags = lib.concatStringsSep " " [
+            "--pause-image" "test.local/pause:local"
+            "--node-ip" "192.168.1.2"
+          ];
+        };
+        networking.firewall.allowedTCPPorts = [ 6443 ];
+        networking.firewall.allowedUDPPorts = [ 8472 ];
+        networking.firewall.trustedInterfaces = [ "flannel.1" ];
+        networking.useDHCP = false;
+        networking.defaultGateway = "192.168.1.2";
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkForce [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+    };
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ euank ];
+    };
+
+    testScript = ''
+      machines = [server, server2, agent]
+      for m in machines:
+          m.start()
+          m.wait_for_unit("k3s")
+
+      is_aarch64 = "${toString pkgs.stdenv.isAarch64}" == "1"
+
+      # wait for the agent to show up
+      server.wait_until_succeeds("k3s kubectl get node agent")
+
+      for m in machines:
+          # Fix-Me: Tests fail for 'aarch64-linux' as: "CONFIG_CGROUP_FREEZER: missing (fail)"
+          if not is_aarch64:
+              m.succeed("k3s check-config")
+          m.succeed(
+              "${pauseImage} | k3s ctr image import -"
+          )
+
+      server.succeed("k3s kubectl cluster-info")
+      # Also wait for our service account to show up; it takes a sec
+      server.wait_until_succeeds("k3s kubectl get serviceaccount default")
+
+      # Now create a pod on each node via a daemonset and verify they can talk to each other.
+      server.succeed("k3s kubectl apply -f ${networkTestDaemonset}")
+      server.wait_until_succeeds(f'[ "$(k3s kubectl get ds test -o json | jq .status.numberReady)" -eq {len(machines)} ]')
+
+      # Get pod IPs
+      pods = server.succeed("k3s kubectl get po -o json | jq '.items[].metadata.name' -r").splitlines()
+      pod_ips = [server.succeed(f"k3s kubectl get po {name} -o json | jq '.status.podIP' -cr").strip() for name in pods]
+
+      # Verify each server can ping each pod ip
+      for pod_ip in pod_ips:
+          server.succeed(f"ping -c 1 {pod_ip}")
+          agent.succeed(f"ping -c 1 {pod_ip}")
+
+      # Verify the pods can talk to each other
+      resp = server.wait_until_succeeds(f"k3s kubectl exec {pods[0]} -- socat TCP:{pod_ips[1]}:8000 -")
+      assert resp.strip() == "server"
+      resp = server.wait_until_succeeds(f"k3s kubectl exec {pods[1]} -- socat TCP:{pod_ips[0]}:8000 -")
+      assert resp.strip() == "server"
+
+      # Cleanup
+      server.succeed("k3s kubectl delete -f ${networkTestDaemonset}")
+
+      for m in machines:
+          m.shutdown()
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/k3s/single-node.nix b/nixpkgs/nixos/tests/k3s/single-node.nix
new file mode 100644
index 000000000000..e059603b9c9d
--- /dev/null
+++ b/nixpkgs/nixos/tests/k3s/single-node.nix
@@ -0,0 +1,85 @@
+import ../make-test-python.nix ({ pkgs, lib, k3s, ... }:
+  let
+    imageEnv = pkgs.buildEnv {
+      name = "k3s-pause-image-env";
+      paths = with pkgs; [ tini (hiPrio coreutils) busybox ];
+    };
+    pauseImage = pkgs.dockerTools.streamLayeredImage {
+      name = "test.local/pause";
+      tag = "local";
+      contents = imageEnv;
+      config.Entrypoint = [ "/bin/tini" "--" "/bin/sleep" "inf" ];
+    };
+    testPodYaml = pkgs.writeText "test.yml" ''
+      apiVersion: v1
+      kind: Pod
+      metadata:
+        name: test
+      spec:
+        containers:
+        - name: test
+          image: test.local/pause:local
+          imagePullPolicy: Never
+          command: ["sh", "-c", "sleep inf"]
+    '';
+  in
+  {
+    name = "${k3s.name}-single-node";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ euank ];
+    };
+
+    nodes.machine = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ k3s gzip ];
+
+      # k3s uses enough resources the default vm fails.
+      virtualisation.memorySize = 1536;
+      virtualisation.diskSize = 4096;
+
+      services.k3s.enable = true;
+      services.k3s.role = "server";
+      services.k3s.package = k3s;
+      # Slightly reduce resource usage
+      services.k3s.extraFlags = builtins.toString [
+        "--disable" "coredns"
+        "--disable" "local-storage"
+        "--disable" "metrics-server"
+        "--disable" "servicelb"
+        "--disable" "traefik"
+        "--pause-image" "test.local/pause:local"
+      ];
+
+      users.users = {
+        noprivs = {
+          isNormalUser = true;
+          description = "Can't access k3s by default";
+          password = "*";
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("k3s")
+      machine.succeed("kubectl cluster-info")
+      machine.fail("sudo -u noprivs kubectl cluster-info")
+      '' # Fix-Me: Tests fail for 'aarch64-linux' as: "CONFIG_CGROUP_FREEZER: missing (fail)"
+      + lib.optionalString (!pkgs.stdenv.isAarch64) ''machine.succeed("k3s check-config")'' + ''
+
+      machine.succeed(
+          "${pauseImage} | ctr image import -"
+      )
+
+      # Also wait for our service account to show up; it takes a sec
+      machine.wait_until_succeeds("kubectl get serviceaccount default")
+      machine.succeed("kubectl apply -f ${testPodYaml}")
+      machine.succeed("kubectl wait --for 'condition=Ready' pod/test")
+      machine.succeed("kubectl delete -f ${testPodYaml}")
+
+      # regression test for #176445
+      machine.fail("journalctl -o cat -u k3s.service | grep 'ipset utility not found'")
+
+      machine.shutdown()
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/kafka.nix b/nixpkgs/nixos/tests/kafka.nix
new file mode 100644
index 000000000000..f4f9827ab7b5
--- /dev/null
+++ b/nixpkgs/nixos/tests/kafka.nix
@@ -0,0 +1,115 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with pkgs.lib;
+
+let
+  makeKafkaTest = name: { kafkaPackage, mode ? "zookeeper" }: (import ./make-test-python.nix ({
+    inherit name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ nequissimus ];
+    };
+
+    nodes = {
+      kafka = { ... }: {
+        services.apache-kafka = mkMerge [
+          ({
+            enable = true;
+            package = kafkaPackage;
+            settings = {
+              "offsets.topic.replication.factor" = 1;
+              "log.dirs" = [
+                "/var/lib/kafka/logdir1"
+                "/var/lib/kafka/logdir2"
+              ];
+            };
+          })
+          (mkIf (mode == "zookeeper") {
+            settings = {
+              "zookeeper.session.timeout.ms" = 600000;
+              "zookeeper.connect" = [ "zookeeper1:2181" ];
+            };
+          })
+          (mkIf (mode == "kraft") {
+            clusterId = "ak2fIHr4S8WWarOF_ODD0g";
+            formatLogDirs = true;
+            settings = {
+              "node.id" = 1;
+              "process.roles" = [
+                "broker"
+                "controller"
+              ];
+              "listeners" = [
+                "PLAINTEXT://:9092"
+                "CONTROLLER://:9093"
+              ];
+              "listener.security.protocol.map" = [
+                "PLAINTEXT:PLAINTEXT"
+                "CONTROLLER:PLAINTEXT"
+              ];
+              "controller.quorum.voters" = [
+                "1@kafka:9093"
+              ];
+              "controller.listener.names" = [ "CONTROLLER" ];
+            };
+          })
+        ];
+
+        networking.firewall.allowedTCPPorts = [ 9092 9093 ];
+        # i686 tests: qemu-system-i386 can simulate max 2047MB RAM (not 2048)
+        virtualisation.memorySize = 2047;
+      };
+    } // optionalAttrs (mode == "zookeeper") {
+      zookeeper1 = { ... }: {
+        services.zookeeper = {
+          enable = true;
+        };
+
+        networking.firewall.allowedTCPPorts = [ 2181 ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${optionalString (mode == "zookeeper") ''
+      zookeeper1.wait_for_unit("default.target")
+      zookeeper1.wait_for_unit("zookeeper.service")
+      zookeeper1.wait_for_open_port(2181)
+      ''}
+
+      kafka.wait_for_unit("default.target")
+      kafka.wait_for_unit("apache-kafka.service")
+      kafka.wait_for_open_port(9092)
+
+      kafka.wait_until_succeeds(
+          "${kafkaPackage}/bin/kafka-topics.sh --create "
+          + "--bootstrap-server localhost:9092 --partitions 1 "
+          + "--replication-factor 1 --topic testtopic"
+      )
+      kafka.succeed(
+          "echo 'test 1' | "
+          + "${kafkaPackage}/bin/kafka-console-producer.sh "
+          + "--broker-list localhost:9092 --topic testtopic"
+      )
+      assert "test 1" in kafka.succeed(
+          "${kafkaPackage}/bin/kafka-console-consumer.sh "
+          + "--bootstrap-server localhost:9092 --topic testtopic "
+          + "--from-beginning --max-messages 1"
+      )
+    '';
+  }) { inherit system; });
+
+in with pkgs; {
+  kafka_2_8 = makeKafkaTest "kafka_2_8" { kafkaPackage = apacheKafka_2_8; };
+  kafka_3_0 = makeKafkaTest "kafka_3_0" { kafkaPackage = apacheKafka_3_0; };
+  kafka_3_1 = makeKafkaTest "kafka_3_1" { kafkaPackage = apacheKafka_3_1; };
+  kafka_3_2 = makeKafkaTest "kafka_3_2" { kafkaPackage = apacheKafka_3_2; };
+  kafka_3_3 = makeKafkaTest "kafka_3_3" { kafkaPackage = apacheKafka_3_3; };
+  kafka_3_4 = makeKafkaTest "kafka_3_4" { kafkaPackage = apacheKafka_3_4; };
+  kafka_3_5 = makeKafkaTest "kafka_3_5" { kafkaPackage = apacheKafka_3_5; };
+  kafka = makeKafkaTest "kafka" { kafkaPackage = apacheKafka; };
+  kafka_kraft = makeKafkaTest "kafka_kraft" { kafkaPackage = apacheKafka; mode = "kraft"; };
+}
diff --git a/nixpkgs/nixos/tests/kanidm.nix b/nixpkgs/nixos/tests/kanidm.nix
new file mode 100644
index 000000000000..fa24d4a8a5e1
--- /dev/null
+++ b/nixpkgs/nixos/tests/kanidm.nix
@@ -0,0 +1,129 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    certs = import ./common/acme/server/snakeoil-certs.nix;
+    serverDomain = certs.domain;
+
+    testCredentials = {
+      password = "Password1_cZPEwpCWvrReripJmAZdmVIZd8HHoHcl";
+    };
+  in
+  {
+    name = "kanidm";
+    meta.maintainers = with pkgs.lib.maintainers; [ erictapen Flakebi ];
+
+    nodes.server = { config, pkgs, lib, ... }: {
+      services.kanidm = {
+        enableServer = true;
+        serverSettings = {
+          origin = "https://${serverDomain}";
+          domain = serverDomain;
+          bindaddress = "[::]:443";
+          ldapbindaddress = "[::1]:636";
+          tls_chain = certs."${serverDomain}".cert;
+          tls_key = certs."${serverDomain}".key;
+        };
+      };
+
+      security.pki.certificateFiles = [ certs.ca.cert ];
+
+      networking.hosts."::1" = [ serverDomain ];
+      networking.firewall.allowedTCPPorts = [ 443 ];
+
+      users.users.kanidm.shell = pkgs.bashInteractive;
+
+      environment.systemPackages = with pkgs; [ kanidm openldap ripgrep ];
+    };
+
+    nodes.client = { pkgs, nodes, ... }: {
+      services.kanidm = {
+        enableClient = true;
+        clientSettings = {
+          uri = "https://${serverDomain}";
+          verify_ca = true;
+          verify_hostnames = true;
+        };
+        enablePam = true;
+        unixSettings = {
+          pam_allowed_login_groups = [ "shell" ];
+        };
+      };
+
+      networking.hosts."${nodes.server.networking.primaryIPAddress}" = [ serverDomain ];
+
+      security.pki.certificateFiles = [ certs.ca.cert ];
+    };
+
+    testScript = { nodes, ... }:
+      let
+        ldapBaseDN = builtins.concatStringsSep "," (map (s: "dc=" + s) (pkgs.lib.splitString "." serverDomain));
+
+        # We need access to the config file in the test script.
+        filteredConfig = pkgs.lib.converge
+          (pkgs.lib.filterAttrsRecursive (_: v: v != null))
+          nodes.server.services.kanidm.serverSettings;
+        serverConfigFile = (pkgs.formats.toml { }).generate "server.toml" filteredConfig;
+
+      in
+      ''
+        start_all()
+        server.wait_for_unit("kanidm.service")
+        client.systemctl("start network-online.target")
+        client.wait_for_unit("network-online.target")
+
+        with subtest("Test HTTP interface"):
+            server.wait_until_succeeds("curl -Lsf https://${serverDomain} | grep Kanidm")
+
+        with subtest("Test LDAP interface"):
+            server.succeed("ldapsearch -H ldaps://${serverDomain}:636 -b '${ldapBaseDN}' -x '(name=test)'")
+
+        with subtest("Test CLI login"):
+            client.succeed("kanidm login -D anonymous")
+            client.succeed("kanidm self whoami | grep anonymous@${serverDomain}")
+            client.succeed("kanidm logout")
+
+        with subtest("Recover idm_admin account"):
+            idm_admin_password = server.succeed("su - kanidm -c 'kanidmd recover-account -c ${serverConfigFile} idm_admin 2>&1 | rg -o \'[A-Za-z0-9]{48}\' '").strip().removeprefix("'").removesuffix("'")
+
+        with subtest("Test unixd connection"):
+            client.wait_for_unit("kanidm-unixd.service")
+            client.wait_for_file("/run/kanidm-unixd/sock")
+            client.wait_until_succeeds("kanidm-unix status | grep working!")
+
+        with subtest("Test user creation"):
+            client.wait_for_unit("getty@tty1.service")
+            client.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+            client.wait_until_tty_matches("1", "login: ")
+            client.send_chars("root\n")
+            client.send_chars("kanidm login -D idm_admin\n")
+            client.wait_until_tty_matches("1", "Enter password: ")
+            client.send_chars(f"{idm_admin_password}\n")
+            client.wait_until_tty_matches("1", "Login Success for idm_admin")
+            client.succeed("kanidm person create testuser TestUser")
+            client.succeed("kanidm person posix set --shell \"$SHELL\" testuser")
+            client.send_chars("kanidm person posix set-password testuser\n")
+            client.wait_until_tty_matches("1", "Enter new")
+            client.send_chars("${testCredentials.password}\n")
+            client.wait_until_tty_matches("1", "Retype")
+            client.send_chars("${testCredentials.password}\n")
+            output = client.succeed("getent passwd testuser")
+            assert "TestUser" in output
+            client.succeed("kanidm group create shell")
+            client.succeed("kanidm group posix set shell")
+            client.succeed("kanidm group add-members shell testuser")
+
+        with subtest("Test user login"):
+            client.send_key("alt-f2")
+            client.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+            client.wait_for_unit("getty@tty2.service")
+            client.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+            client.wait_until_tty_matches("2", "login: ")
+            client.send_chars("testuser\n")
+            client.wait_until_tty_matches("2", "login: testuser")
+            client.wait_until_succeeds("pgrep login")
+            client.wait_until_tty_matches("2", "Password: ")
+            client.send_chars("${testCredentials.password}\n")
+            client.wait_until_succeeds("systemctl is-active user@$(id -u testuser).service")
+            client.send_chars("touch done\n")
+            client.wait_for_file("/home/testuser@${serverDomain}/done")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/karma.nix b/nixpkgs/nixos/tests/karma.nix
new file mode 100644
index 000000000000..5ac2983b8aa3
--- /dev/null
+++ b/nixpkgs/nixos/tests/karma.nix
@@ -0,0 +1,84 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "karma";
+  nodes = {
+    server = { ... }: {
+      services.prometheus.alertmanager = {
+        enable = true;
+        logLevel = "debug";
+        port = 9093;
+        openFirewall = true;
+        configuration = {
+          global = {
+            resolve_timeout = "1m";
+          };
+          route = {
+            # Root route node
+            receiver = "test";
+            group_by = ["..."];
+            continue = false;
+            group_wait = "1s";
+            group_interval="15s";
+            repeat_interval = "24h";
+          };
+          receivers = [
+            {
+              name = "test";
+              webhook_configs = [
+                {
+                  url = "http://localhost:1234";
+                  send_resolved = true;
+                  max_alerts = 0;
+                }
+              ];
+            }
+          ];
+        };
+      };
+      services.karma = {
+        enable = true;
+        openFirewall = true;
+        settings = {
+          listen = {
+            address = "0.0.0.0";
+            port = 8081;
+          };
+          alertmanager = {
+            servers = [
+              {
+                name = "alertmanager";
+                uri = "https://127.0.0.1:9093";
+              }
+            ];
+          };
+          karma.name = "test-dashboard";
+          log.config = true;
+          log.requests = true;
+          log.timestamp = true;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("Wait for server to come up"):
+
+      server.wait_for_unit("alertmanager.service")
+      server.wait_for_unit("karma.service")
+
+      server.sleep(5) # wait for both services to settle
+
+      server.wait_for_open_port(9093)
+      server.wait_for_open_port(8081)
+
+    with subtest("Test alertmanager readiness"):
+      server.succeed("curl -s http://127.0.0.1:9093/-/ready")
+
+      # Karma only starts serving the dashboard once it has established connectivity to all alertmanagers in its config
+      # Therefore, this will fail if karma isn't able to reach alertmanager
+      server.succeed("curl -s http://127.0.0.1:8081")
+
+    server.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kavita.nix b/nixpkgs/nixos/tests/kavita.nix
new file mode 100644
index 000000000000..f27b3fffbcf6
--- /dev/null
+++ b/nixpkgs/nixos/tests/kavita.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "kavita";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ misterio77 ];
+  };
+
+  nodes = {
+    kavita = { config, pkgs, ... }: {
+      services.kavita = {
+        enable = true;
+        port = 5000;
+        tokenKeyFile = builtins.toFile "kavita.key" "QfpjFvjT83BLtZ74GE3U3Q==";
+      };
+    };
+  };
+
+  testScript = let
+    regUrl = "http://kavita:5000/api/Account/register";
+    payload = builtins.toFile "payload.json" (builtins.toJSON {
+      username = "foo";
+      password = "correcthorsebatterystaple";
+      email = "foo@bar";
+    });
+  in ''
+    kavita.start
+    kavita.wait_for_unit("kavita.service")
+
+    # Check that static assets are working
+    kavita.wait_until_succeeds("curl http://kavita:5000/site.webmanifest | grep Kavita")
+
+    # Check that registration is working
+    kavita.succeed("curl -fX POST ${regUrl} --json @${payload}")
+    # But only for the first one
+    kavita.fail("curl -fX POST ${regUrl} --json @${payload}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kbd-setfont-decompress.nix b/nixpkgs/nixos/tests/kbd-setfont-decompress.nix
new file mode 100644
index 000000000000..810ef39cc11a
--- /dev/null
+++ b/nixpkgs/nixos/tests/kbd-setfont-decompress.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "kbd-setfont-decompress";
+
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine = { ... }: {};
+
+  testScript = ''
+    machine.succeed("gzip -cd ${pkgs.terminus_font}/share/consolefonts/ter-v16b.psf.gz >font.psf")
+    machine.succeed("gzip <font.psf >font.psf.gz")
+    machine.succeed("bzip2 <font.psf >font.psf.bz2")
+    machine.succeed("xz <font.psf >font.psf.xz")
+    machine.succeed("zstd <font.psf >font.psf.zst")
+    # setfont returns 0 even on error.
+    assert machine.succeed("PATH= ${pkgs.kbd}/bin/setfont font.psf.gz  2>&1") == ""
+    assert machine.succeed("PATH= ${pkgs.kbd}/bin/setfont font.psf.bz2 2>&1") == ""
+    assert machine.succeed("PATH= ${pkgs.kbd}/bin/setfont font.psf.xz  2>&1") == ""
+    assert machine.succeed("PATH= ${pkgs.kbd}/bin/setfont font.psf.zst 2>&1") == ""
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kbd-update-search-paths-patch.nix b/nixpkgs/nixos/tests/kbd-update-search-paths-patch.nix
new file mode 100644
index 000000000000..746a809c4cdf
--- /dev/null
+++ b/nixpkgs/nixos/tests/kbd-update-search-paths-patch.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "kbd-update-search-paths-patch";
+
+  nodes.machine = { pkgs, options, ... }: {
+    console = {
+      packages = options.console.packages.default ++ [ pkgs.terminus_font ];
+    };
+  };
+
+  testScript = ''
+    command = "${pkgs.kbd}/bin/setfont ter-112n 2>&1"
+    (status, out) = machine.execute(command)
+    import re
+    pattern = re.compile(r".*Unable to find file:.*")
+    match = pattern.match(out)
+    if match:
+        raise Exception("command `{}` failed".format(command))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kea.nix b/nixpkgs/nixos/tests/kea.nix
new file mode 100644
index 000000000000..c8ecf771fa13
--- /dev/null
+++ b/nixpkgs/nixos/tests/kea.nix
@@ -0,0 +1,186 @@
+# This test verifies DHCPv4 interaction between a client and a router.
+# For successful DHCP allocations a dynamic update request is sent
+# towards a nameserver to allocate a name in the lan.nixos.test zone.
+# We then verify whether client and router can ping each other, and
+# that the nameserver can resolve the clients fqdn to the correct IP
+# address.
+
+import ./make-test-python.nix ({ pkgs, lib, ...}: {
+  meta.maintainers = with lib.maintainers; [ hexa ];
+
+  name = "kea";
+
+  nodes = {
+    router = { config, pkgs, ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useDHCP = false;
+        firewall.allowedUDPPorts = [ 67 ];
+      };
+
+      systemd.network = {
+        enable = true;
+        networks = {
+          "01-eth1" = {
+            name = "eth1";
+            networkConfig = {
+              Address = "10.0.0.1/29";
+            };
+          };
+        };
+      };
+
+      services.kea.dhcp4 = {
+        enable = true;
+        settings = {
+          valid-lifetime = 3600;
+          renew-timer = 900;
+          rebind-timer = 1800;
+
+          lease-database = {
+            type = "memfile";
+            persist = true;
+            name = "/var/lib/kea/dhcp4.leases";
+          };
+
+          interfaces-config = {
+            dhcp-socket-type = "raw";
+            interfaces = [
+              "eth1"
+            ];
+          };
+
+          subnet4 = [ {
+            subnet = "10.0.0.0/29";
+            pools = [ {
+              pool = "10.0.0.3 - 10.0.0.3";
+            } ];
+          } ];
+
+          # Enable communication between dhcp4 and a local dhcp-ddns
+          # instance.
+          # https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp4-srv.html#ddns-for-dhcpv4
+          dhcp-ddns = {
+            enable-updates = true;
+          };
+
+          ddns-send-updates = true;
+          ddns-qualifying-suffix = "lan.nixos.test.";
+        };
+      };
+
+      services.kea.dhcp-ddns = {
+        enable = true;
+        settings = {
+          forward-ddns = {
+            # Configure updates of a forward zone named `lan.nixos.test`
+            # hosted at the nameserver at 10.0.0.2
+            # https://kea.readthedocs.io/en/kea-2.2.0/arm/ddns.html#adding-forward-dns-servers
+            ddns-domains = [ {
+              name = "lan.nixos.test.";
+              # Use a TSIG key in production!
+              key-name = "";
+              dns-servers = [ {
+                ip-address = "10.0.0.2";
+                port = 53;
+              } ];
+            } ];
+          };
+        };
+      };
+    };
+
+    nameserver = { config, pkgs, ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useDHCP = false;
+        firewall.allowedUDPPorts = [ 53 ];
+      };
+
+      systemd.network = {
+        enable = true;
+        networks = {
+          "01-eth1" = {
+            name = "eth1";
+            networkConfig = {
+              Address = "10.0.0.2/29";
+            };
+          };
+        };
+      };
+
+      services.resolved.enable = false;
+
+      # Set up an authoritative nameserver, serving the `lan.nixos.test`
+      # zone and configure an ACL that allows dynamic updates from
+      # the router's ip address.
+      # This ACL is likely insufficient for production usage. Please
+      # use TSIG keys.
+      services.knot = let
+        zone = pkgs.writeTextDir "lan.nixos.test.zone" ''
+          @ SOA ns.nixos.test nox.nixos.test 0 86400 7200 3600000 172800
+          @ NS nameserver
+          nameserver A 10.0.0.3
+          router A 10.0.0.1
+        '';
+        zonesDir = pkgs.buildEnv {
+          name = "knot-zones";
+          paths = [ zone ];
+        };
+      in {
+        enable = true;
+        extraArgs = [
+          "-v"
+        ];
+        settings = {
+          server.listen = [
+            "0.0.0.0@53"
+          ];
+
+          log.syslog.any = "info";
+
+          acl.dhcp_ddns = {
+            address = "10.0.0.1";
+            action = "update";
+          };
+
+          template.default = {
+            storage = zonesDir;
+            zonefile-sync = "-1";
+            zonefile-load = "difference-no-serial";
+            journal-content = "all";
+          };
+
+          zone."lan.nixos.test" = {
+            file = "lan.nixos.test.zone";
+            acl = [
+              "dhcp_ddns"
+            ];
+          };
+        };
+      };
+
+    };
+
+    client = { config, pkgs, ... }: {
+      virtualisation.vlans = [ 1 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1.useDHCP = true;
+      };
+    };
+  };
+  testScript = { ... }: ''
+    start_all()
+    router.wait_for_unit("kea-dhcp4-server.service")
+    client.wait_for_unit("systemd-networkd-wait-online.service")
+    client.wait_until_succeeds("ping -c 5 10.0.0.1")
+    router.wait_until_succeeds("ping -c 5 10.0.0.3")
+    nameserver.wait_until_succeeds("kdig +short client.lan.nixos.test @10.0.0.2 | grep -q 10.0.0.3")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/keepalived.nix b/nixpkgs/nixos/tests/keepalived.nix
new file mode 100644
index 000000000000..16564511d85d
--- /dev/null
+++ b/nixpkgs/nixos/tests/keepalived.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "keepalived";
+  meta.maintainers = [ lib.maintainers.raitobezarius ];
+
+  nodes = {
+    node1 = { pkgs, ... }: {
+      networking.firewall.extraCommands = "iptables -A INPUT -p vrrp -j ACCEPT";
+      services.keepalived.enable = true;
+      services.keepalived.vrrpInstances.test = {
+        interface = "eth1";
+        state = "MASTER";
+        priority = 50;
+        virtualIps = [{ addr = "192.168.1.200"; }];
+        virtualRouterId = 1;
+      };
+      environment.systemPackages = [ pkgs.tcpdump ];
+    };
+    node2 = { pkgs, ... }: {
+      networking.firewall.extraCommands = "iptables -A INPUT -p vrrp -j ACCEPT";
+      services.keepalived.enable = true;
+      services.keepalived.vrrpInstances.test = {
+        interface = "eth1";
+        state = "MASTER";
+        priority = 100;
+        virtualIps = [{ addr = "192.168.1.200"; }];
+        virtualRouterId = 1;
+      };
+      environment.systemPackages = [ pkgs.tcpdump ];
+    };
+  };
+
+  testScript = ''
+    # wait for boot time delay to pass
+    for node in [node1, node2]:
+        node.wait_until_succeeds(
+            "systemctl show -p LastTriggerUSecMonotonic keepalived-boot-delay.timer | grep -vq 'LastTriggerUSecMonotonic=0'"
+        )
+        node.wait_for_unit("keepalived")
+    node2.wait_until_succeeds("ip addr show dev eth1 | grep -q 192.168.1.200")
+    node1.fail("ip addr show dev eth1 | grep -q 192.168.1.200")
+    node1.succeed("ping -c1 192.168.1.200")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/keepassxc.nix b/nixpkgs/nixos/tests/keepassxc.nix
new file mode 100644
index 000000000000..a4f452412cdf
--- /dev/null
+++ b/nixpkgs/nixos/tests/keepassxc.nix
@@ -0,0 +1,72 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "keepassxc";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ turion ];
+    timeout = 1800;
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [
+      ./common/user-account.nix
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/163482
+    qt = {
+      enable = true;
+      platformTheme = "gnome";
+      style = "adwaita-dark";
+    };
+
+    test-support.displayManager.auto.user = "alice";
+    environment.systemPackages = with pkgs; [
+      keepassxc
+      xdotool
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    aliceDo = cmd: ''machine.succeed("su - alice -c '${cmd}' >&2 &");'';
+    in ''
+    with subtest("Ensure X starts"):
+        start_all()
+        machine.wait_for_x()
+
+    with subtest("Can create database and entry with CLI"):
+        ${aliceDo "keepassxc-cli db-create -k foo.keyfile foo.kdbx"}
+        ${aliceDo "keepassxc-cli add --no-password -k foo.keyfile foo.kdbx bar"}
+
+    with subtest("Ensure KeePassXC starts"):
+        # start KeePassXC window
+        ${aliceDo "keepassxc >&2 &"}
+
+        machine.wait_for_text("KeePassXC ${pkgs.keepassxc.version}")
+        machine.screenshot("KeePassXC")
+
+    with subtest("Can open existing database"):
+        machine.send_key("ctrl-o")
+        machine.sleep(5)
+        # Regression #163482: keepassxc did not crash
+        machine.succeed("ps -e | grep keepassxc")
+        machine.wait_for_text("Open database")
+        machine.send_key("ret")
+
+        # Wait for the enter password screen to appear.
+        machine.wait_for_text("/home/alice/foo.kdbx")
+
+        # Click on "Browse" button to select keyfile
+        machine.send_key("tab")
+        machine.send_chars("/home/alice/foo.keyfile")
+        machine.send_key("ret")
+        # Database is unlocked (doesn't have "[Locked]" in the title anymore)
+        machine.wait_for_text("foo.kdbx - KeePassXC")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kerberos/default.nix b/nixpkgs/nixos/tests/kerberos/default.nix
new file mode 100644
index 000000000000..f2f1a438918c
--- /dev/null
+++ b/nixpkgs/nixos/tests/kerberos/default.nix
@@ -0,0 +1,7 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+{
+  mit = import ./mit.nix { inherit system pkgs; };
+  heimdal = import ./heimdal.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/kerberos/heimdal.nix b/nixpkgs/nixos/tests/kerberos/heimdal.nix
new file mode 100644
index 000000000000..393289f7a92c
--- /dev/null
+++ b/nixpkgs/nixos/tests/kerberos/heimdal.nix
@@ -0,0 +1,47 @@
+import ../make-test-python.nix ({pkgs, ...}: {
+  name = "kerberos_server-heimdal";
+
+  nodes.machine = { config, libs, pkgs, ...}:
+  { services.kerberos_server =
+    { enable = true;
+      realms = {
+        "FOO.BAR".acl = [{principal = "admin"; access = ["add" "cpw"];}];
+      };
+    };
+    security.krb5 = {
+      enable = true;
+      package = pkgs.heimdal;
+      settings = {
+        libdefaults = {
+          default_realm = "FOO.BAR";
+        };
+        realms = {
+          "FOO.BAR" = {
+            admin_server = "machine";
+            kdc = "machine";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    machine.succeed(
+        "kadmin -l init --realm-max-ticket-life='8 day' --realm-max-renewable-life='10 day' FOO.BAR",
+        "systemctl restart kadmind.service kdc.service",
+    )
+
+    for unit in ["kadmind", "kdc", "kpasswdd"]:
+        machine.wait_for_unit(f"{unit}.service")
+
+    machine.succeed(
+        "kadmin -l add --password=admin_pw --use-defaults admin",
+        "kadmin -l ext_keytab --keytab=admin.keytab admin",
+        "kadmin -p admin -K admin.keytab add --password=alice_pw --use-defaults alice",
+        "kadmin -l ext_keytab --keytab=alice.keytab alice",
+        "kinit -kt alice.keytab alice",
+    )
+  '';
+
+  meta.maintainers = [ pkgs.lib.maintainers.dblsaiko ];
+})
diff --git a/nixpkgs/nixos/tests/kerberos/mit.nix b/nixpkgs/nixos/tests/kerberos/mit.nix
new file mode 100644
index 000000000000..1191d047abbf
--- /dev/null
+++ b/nixpkgs/nixos/tests/kerberos/mit.nix
@@ -0,0 +1,46 @@
+import ../make-test-python.nix ({pkgs, ...}: {
+  name = "kerberos_server-mit";
+
+  nodes.machine = { config, libs, pkgs, ...}:
+  { services.kerberos_server =
+    { enable = true;
+      realms = {
+        "FOO.BAR".acl = [{principal = "admin"; access = ["add" "cpw"];}];
+      };
+    };
+    security.krb5 = {
+      enable = true;
+      package = pkgs.krb5;
+      settings = {
+        libdefaults = {
+          default_realm = "FOO.BAR";
+        };
+        realms = {
+          "FOO.BAR" = {
+            admin_server = "machine";
+            kdc = "machine";
+          };
+        };
+      };
+    };
+    users.extraUsers.alice = { isNormalUser = true; };
+  };
+
+  testScript = ''
+    machine.succeed(
+        "kdb5_util create -s -r FOO.BAR -P master_key",
+        "systemctl restart kadmind.service kdc.service",
+    )
+
+    for unit in ["kadmind", "kdc"]:
+        machine.wait_for_unit(f"{unit}.service")
+
+    machine.succeed(
+        "kadmin.local add_principal -pw admin_pw admin",
+        "kadmin -p admin -w admin_pw addprinc -pw alice_pw alice",
+        "echo alice_pw | sudo -u alice kinit",
+    )
+  '';
+
+  meta.maintainers = [ pkgs.lib.maintainers.dblsaiko ];
+})
diff --git a/nixpkgs/nixos/tests/kernel-generic.nix b/nixpkgs/nixos/tests/kernel-generic.nix
new file mode 100644
index 000000000000..0dcab39f3fad
--- /dev/null
+++ b/nixpkgs/nixos/tests/kernel-generic.nix
@@ -0,0 +1,51 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}@args:
+
+with pkgs.lib;
+
+let
+  testsForLinuxPackages = linuxPackages: (import ./make-test-python.nix ({ pkgs, ... }: {
+    name = "kernel-${linuxPackages.kernel.version}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ nequissimus atemu ma27 ];
+    };
+
+    nodes.machine = { ... }:
+      {
+        boot.kernelPackages = linuxPackages;
+      };
+
+    testScript =
+      ''
+        assert "Linux" in machine.succeed("uname -s")
+        assert "${linuxPackages.kernel.modDirVersion}" in machine.succeed("uname -a")
+      '';
+  }) args);
+  kernels = (removeAttrs pkgs.linuxKernel.vanillaPackages ["__attrsFailEvaluation"]) // {
+    inherit (pkgs.linuxKernel.packages)
+      linux_4_19_hardened
+      linux_5_4_hardened
+      linux_5_10_hardened
+      linux_5_15_hardened
+      linux_6_1_hardened
+      linux_6_5_hardened
+      linux_6_6_hardened
+      linux_6_7_hardened
+      linux_rt_5_4
+      linux_rt_5_10
+      linux_rt_5_15
+      linux_rt_6_1
+      linux_libre
+
+      linux_testing;
+  };
+
+in mapAttrs (_: lP: testsForLinuxPackages lP) kernels // {
+  passthru = {
+    inherit testsForLinuxPackages;
+
+    testsForKernel = kernel: testsForLinuxPackages (pkgs.linuxPackagesFor kernel);
+  };
+}
diff --git a/nixpkgs/nixos/tests/kernel-latest-ath-user-regd.nix b/nixpkgs/nixos/tests/kernel-latest-ath-user-regd.nix
new file mode 100644
index 000000000000..09e1da9d2aff
--- /dev/null
+++ b/nixpkgs/nixos/tests/kernel-latest-ath-user-regd.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "kernel-latest-ath-user-regd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ veehaitch ];
+  };
+
+  nodes.machine = { pkgs, ... }:
+    {
+      boot.kernelPackages = pkgs.linuxPackages_latest;
+      networking.wireless.athUserRegulatoryDomain = true;
+    };
+
+  testScript =
+    ''
+      assert "CONFIG_ATH_USER_REGD=y" in machine.succeed("zcat /proc/config.gz")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/kernel-rust.nix b/nixpkgs/nixos/tests/kernel-rust.nix
new file mode 100644
index 000000000000..1f269173ec2e
--- /dev/null
+++ b/nixpkgs/nixos/tests/kernel-rust.nix
@@ -0,0 +1,43 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+let
+  inherit (pkgs.lib) const filterAttrs mapAttrs;
+
+  kernelRustTest = kernelPackages: import ./make-test-python.nix ({ lib, ... }: {
+    name = "kernel-rust";
+    meta.maintainers = with lib.maintainers; [ blitz ma27 ];
+    nodes.machine = { config, ... }: {
+      boot = {
+        inherit kernelPackages;
+        extraModulePackages = [ config.boot.kernelPackages.rust-out-of-tree-module ];
+        kernelPatches = [
+          {
+            name = "Rust Support";
+            patch = null;
+            features = {
+              rust = true;
+            };
+          }
+        ];
+      };
+    };
+    testScript = ''
+      machine.wait_for_unit("default.target")
+      machine.succeed("modprobe rust_out_of_tree")
+    '';
+  });
+
+  kernels = {
+    inherit (pkgs.linuxKernel.packages) linux_testing;
+  }
+  // filterAttrs
+    (const (x: let
+      inherit (builtins.tryEval (
+        x.rust-out-of-tree-module or null != null
+      )) success value;
+    in success && value))
+    pkgs.linuxKernel.vanillaPackages;
+in mapAttrs (const kernelRustTest) kernels
diff --git a/nixpkgs/nixos/tests/keter.nix b/nixpkgs/nixos/tests/keter.nix
new file mode 100644
index 000000000000..1cc2ffbde0a0
--- /dev/null
+++ b/nixpkgs/nixos/tests/keter.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    port = 81;
+  in
+  {
+    name = "keter";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ jappie ];
+    };
+
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.keter = {
+        enable = true;
+
+        globalKeterConfig = {
+          cli-port = 123; # just adding this to test the freeform
+          listeners = [{
+            host = "*4";
+            inherit port;
+          }];
+        };
+        bundle = {
+          appName = "test-bundle";
+          domain = "localhost";
+          executable = pkgs.writeShellScript "run" ''
+            ${pkgs.python3}/bin/python -m http.server $PORT
+          '';
+        };
+      };
+    };
+
+    testScript =
+      ''
+        machine.wait_for_unit("keter.service")
+
+        machine.wait_for_open_port(${toString port})
+        machine.wait_for_console_text("Activating app test-bundle with hosts: localhost")
+
+
+        machine.succeed("curl --fail http://localhost:${toString port}/")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/kexec.nix b/nixpkgs/nixos/tests/kexec.nix
new file mode 100644
index 000000000000..4d1be497b8ba
--- /dev/null
+++ b/nixpkgs/nixos/tests/kexec.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "kexec";
+  meta = with lib.maintainers; {
+    maintainers = [ flokli lassulus ];
+  };
+
+  nodes = {
+    node1 = { ... }: {
+      virtualisation.vlans = [ ];
+      virtualisation.memorySize = 4 * 1024;
+    };
+
+    node2 = { modulesPath, ... }: {
+      virtualisation.vlans = [ ];
+      environment.systemPackages = [ pkgs.hello ];
+      imports = [
+        "${modulesPath}/installer/netboot/netboot-minimal.nix"
+        "${modulesPath}/testing/test-instrumentation.nix"
+        "${modulesPath}/profiles/qemu-guest.nix"
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    # Test whether reboot via kexec works.
+    node1.wait_for_unit("multi-user.target")
+    node1.succeed('kexec --load /run/current-system/kernel --initrd /run/current-system/initrd --command-line "$(</proc/cmdline)"')
+    node1.execute("systemctl kexec >&2 &", check_return=False)
+    node1.connected = False
+    node1.connect()
+    node1.wait_for_unit("multi-user.target")
+
+    # Check if the machine with netboot-minimal.nix profile boots up
+    node2.wait_for_unit("multi-user.target")
+    node2.shutdown()
+
+    # Kexec node1 to the toplevel of node2 via the kexec-boot script
+    node1.succeed('touch /run/foo')
+    node1.fail('hello')
+    node1.execute('${nodes.node2.system.build.kexecTree}/kexec-boot', check_output=False)
+    node1.connected = False
+    node1.connect()
+    node1.wait_for_unit("multi-user.target")
+    node1.succeed('! test -e /run/foo')
+    node1.succeed('hello')
+    node1.succeed('[ "$(hostname)" = "node2" ]')
+
+    node1.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/keycloak.nix b/nixpkgs/nixos/tests/keycloak.nix
new file mode 100644
index 000000000000..228e57d1cdd6
--- /dev/null
+++ b/nixpkgs/nixos/tests/keycloak.nix
@@ -0,0 +1,183 @@
+# This tests Keycloak: it starts the service, creates a realm with an
+# OIDC client and a user, and simulates the user logging in to the
+# client using their Keycloak login.
+
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  frontendUrl = "https://${certs.domain}";
+
+  keycloakTest = import ./make-test-python.nix (
+    { pkgs, databaseType, ... }:
+    let
+      initialAdminPassword = "h4Iho\"JFn't2>iQIR9";
+      adminPasswordFile = pkgs.writeText "admin-password" "${initialAdminPassword}";
+    in
+    {
+      name = "keycloak";
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ talyz ];
+      };
+
+      nodes = {
+        keycloak = { config, ... }: {
+          security.pki.certificateFiles = [
+            certs.ca.cert
+          ];
+
+          networking.extraHosts = ''
+            127.0.0.1 ${certs.domain}
+          '';
+
+          services.keycloak = {
+            enable = true;
+            settings = {
+              hostname = certs.domain;
+            };
+            inherit initialAdminPassword;
+            sslCertificate = "${certs.${certs.domain}.cert}";
+            sslCertificateKey = "${certs.${certs.domain}.key}";
+            database = {
+              type = databaseType;
+              username = "bogus";
+              name = "also bogus";
+              passwordFile = "${pkgs.writeText "dbPassword" ''wzf6\"vO"Cb\nP>p#6;c&o?eu=q'THE'''H''''E''}";
+            };
+            plugins = with config.services.keycloak.package.plugins; [
+              keycloak-discord
+              keycloak-metrics-spi
+            ];
+          };
+          environment.systemPackages = with pkgs; [
+            xmlstarlet
+            html-tidy
+            jq
+          ];
+        };
+      };
+
+      testScript =
+        let
+          client = {
+            clientId = "test-client";
+            name = "test-client";
+            redirectUris = [ "urn:ietf:wg:oauth:2.0:oob" ];
+          };
+
+          user = {
+            firstName = "Chuck";
+            lastName = "Testa";
+            username = "chuck.testa";
+            email = "chuck.testa@example.com";
+          };
+
+          password = "password1234";
+
+          realm = {
+            enabled = true;
+            realm = "test-realm";
+            clients = [ client ];
+            users = [(
+              user // {
+                enabled = true;
+                credentials = [{
+                  type = "password";
+                  temporary = false;
+                  value = password;
+                }];
+              }
+            )];
+          };
+
+          realmDataJson = pkgs.writeText "realm-data.json" (builtins.toJSON realm);
+
+          jqCheckUserinfo = pkgs.writeText "check-userinfo.jq" ''
+            if {
+              "firstName": .given_name,
+              "lastName": .family_name,
+              "username": .preferred_username,
+              "email": .email
+            } != ${builtins.toJSON user} then
+              error("Wrong user info!")
+            else
+              empty
+            end
+          '';
+        in ''
+          keycloak.start()
+          keycloak.wait_for_unit("keycloak.service")
+          keycloak.wait_for_open_port(443)
+          keycloak.wait_until_succeeds("curl -sSf ${frontendUrl}")
+
+          ### Realm Setup ###
+
+          # Get an admin interface access token
+          keycloak.succeed("""
+              curl -sSf -d 'client_id=admin-cli' \
+                   -d 'username=admin' \
+                   -d "password=$(<${adminPasswordFile})" \
+                   -d 'grant_type=password' \
+                   '${frontendUrl}/realms/master/protocol/openid-connect/token' \
+                   | jq -r '"Authorization: bearer " + .access_token' >admin_auth_header
+          """)
+
+          # Register the metrics SPI
+          keycloak.succeed(
+              """${pkgs.jre}/bin/keytool -import -alias snakeoil -file ${certs.ca.cert} -storepass aaaaaa -keystore cacert.jks -noprompt""",
+              """KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh config credentials --server '${frontendUrl}' --realm master --user admin --password "$(<${adminPasswordFile})" """,
+              """KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh update events/config -s 'eventsEnabled=true' -s 'adminEventsEnabled=true' -s 'eventsListeners+=metrics-listener'""",
+              """curl -sSf '${frontendUrl}/realms/master/metrics' | grep '^keycloak_admin_event_UPDATE'"""
+          )
+
+          # Publish the realm, including a test OIDC client and user
+          keycloak.succeed(
+              "curl -sSf -H @admin_auth_header -X POST -H 'Content-Type: application/json' -d @${realmDataJson} '${frontendUrl}/admin/realms/'"
+          )
+
+          # Generate and save the client secret. To do this we need
+          # Keycloak's internal id for the client.
+          keycloak.succeed(
+              "curl -sSf -H @admin_auth_header '${frontendUrl}/admin/realms/${realm.realm}/clients?clientId=${client.name}' | jq -r '.[].id' >client_id",
+              "curl -sSf -H @admin_auth_header -X POST '${frontendUrl}/admin/realms/${realm.realm}/clients/'$(<client_id)'/client-secret' | jq -r .value >client_secret",
+          )
+
+
+          ### Authentication Testing ###
+
+          # Start the login process by sending an initial request to the
+          # OIDC authentication endpoint, saving the returned page. Tidy
+          # up the HTML (XmlStarlet is picky) and extract the login form
+          # post url.
+          keycloak.succeed(
+              "curl -sSf -c cookie '${frontendUrl}/realms/${realm.realm}/protocol/openid-connect/auth?client_id=${client.name}&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=openid+email&response_type=code&response_mode=query&nonce=qw4o89g3qqm' >login_form",
+              "tidy -asxml -q -m login_form || true",
+              "xml sel -T -t -m \"_:html/_:body/_:div/_:div/_:div/_:div/_:div/_:div/_:form[@id='kc-form-login']\" -v @action login_form >form_post_url",
+          )
+
+          # Post the login form and save the response. Once again tidy up
+          # the HTML, then extract the authorization code.
+          keycloak.succeed(
+              "curl -sSf -L -b cookie -d 'username=${user.username}' -d 'password=${password}' -d 'credentialId=' \"$(<form_post_url)\" >auth_code_html",
+              "tidy -asxml -q -m auth_code_html || true",
+              "xml sel -T -t -m \"_:html/_:body/_:div/_:div/_:div/_:div/_:div/_:input[@id='code']\" -v @value auth_code_html >auth_code",
+          )
+
+          # Exchange the authorization code for an access token.
+          keycloak.succeed(
+              "curl -sSf -d grant_type=authorization_code -d code=$(<auth_code) -d client_id=${client.name} -d client_secret=$(<client_secret) -d redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob '${frontendUrl}/realms/${realm.realm}/protocol/openid-connect/token' | jq -r '\"Authorization: bearer \" + .access_token' >auth_header"
+          )
+
+          # Use the access token on the OIDC userinfo endpoint and check
+          # that the returned user info matches what we initialized the
+          # realm with.
+          keycloak.succeed(
+              "curl -sSf -H @auth_header '${frontendUrl}/realms/${realm.realm}/protocol/openid-connect/userinfo' | jq -f ${jqCheckUserinfo}"
+          )
+        '';
+    }
+  );
+in
+{
+  postgres = keycloakTest { databaseType = "postgresql"; };
+  mariadb = keycloakTest { databaseType = "mariadb"; };
+  mysql = keycloakTest { databaseType = "mysql"; };
+}
diff --git a/nixpkgs/nixos/tests/keyd.nix b/nixpkgs/nixos/tests/keyd.nix
new file mode 100644
index 000000000000..bfc4558b64bb
--- /dev/null
+++ b/nixpkgs/nixos/tests/keyd.nix
@@ -0,0 +1,89 @@
+# The test template is taken from the `./keymap.nix`
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  readyFile = "/tmp/readerReady";
+  resultFile = "/tmp/readerResult";
+
+  testReader = pkgs.writeScript "test-input-reader" ''
+    rm -f ${resultFile} ${resultFile}.tmp
+    logger "testReader: START: Waiting for $1 characters, expecting '$2'."
+    touch ${readyFile}
+    read -r -N $1 chars
+    rm -f ${readyFile}
+    if [ "$chars" == "$2" ]; then
+      logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp
+    else
+      logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp
+    fi
+    # rename after the file is written to prevent a race condition
+    mv  ${resultFile}.tmp ${resultFile}
+  '';
+
+
+  mkKeyboardTest = name: { default, test }: with pkgs.lib; makeTest {
+    inherit name;
+
+    nodes.machine = {
+      services.keyd = {
+        enable = true;
+        keyboards = { inherit default; };
+      };
+    };
+
+    testScript = ''
+      import shlex
+
+      machine.wait_for_unit("keyd.service")
+
+      def run_test_case(cmd, test_case_name, inputs, expected):
+          with subtest(test_case_name):
+              assert len(inputs) == len(expected)
+              machine.execute("rm -f ${readyFile} ${resultFile}")
+              # set up process that expects all the keys to be entered
+              machine.succeed(
+                  "{} {} {} {} >&2 &".format(
+                      cmd,
+                      "${testReader}",
+                      len(inputs),
+                      shlex.quote("".join(expected)),
+                  )
+              )
+              # wait for reader to be ready
+              machine.wait_for_file("${readyFile}")
+              # send all keys
+              for key in inputs:
+                  machine.send_key(key)
+              # wait for result and check
+              machine.wait_for_file("${resultFile}")
+              machine.succeed("grep -q 'PASS:' ${resultFile}")
+      test = ${builtins.toJSON test}
+      run_test_case("openvt -sw --", "${name}", test["press"], test["expect"])
+    '';
+  };
+
+in
+pkgs.lib.mapAttrs mkKeyboardTest {
+  swap-ab_and_ctrl-as-shift = {
+    test.press = [ "a" "ctrl-b" "c" "alt_r-h" ];
+    test.expect = [ "b" "A" "c" "q" ];
+
+    default = {
+      settings.main = {
+        "a" = "b";
+        "b" = "a";
+        "control" = "oneshot(shift)";
+        "rightalt" = "layer(rightalt)";
+      };
+      extraConfig = ''
+        [rightalt:G]
+        h = q
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/keymap.nix b/nixpkgs/nixos/tests/keymap.nix
new file mode 100644
index 000000000000..e8973a50f852
--- /dev/null
+++ b/nixpkgs/nixos/tests/keymap.nix
@@ -0,0 +1,233 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  readyFile  = "/tmp/readerReady";
+  resultFile = "/tmp/readerResult";
+
+  testReader = pkgs.writeScript "test-input-reader" ''
+    rm -f ${resultFile} ${resultFile}.tmp
+    logger "testReader: START: Waiting for $1 characters, expecting '$2'."
+    touch ${readyFile}
+    read -r -N $1 chars
+    rm -f ${readyFile}
+
+    if [ "$chars" == "$2" ]; then
+      logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp
+    else
+      logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp
+    fi
+    # rename after the file is written to prevent a race condition
+    mv  ${resultFile}.tmp ${resultFile}
+  '';
+
+
+  mkKeyboardTest = layout: { extraConfig ? {}, tests }: with pkgs.lib; makeTest {
+    name = "keymap-${layout}";
+
+    nodes.machine.console.keyMap = mkOverride 900 layout;
+    nodes.machine.services.xserver.desktopManager.xterm.enable = false;
+    nodes.machine.services.xserver.xkb.layout = mkOverride 900 layout;
+    nodes.machine.imports = [ ./common/x11.nix extraConfig ];
+
+    testScript = ''
+      import json
+      import shlex
+
+
+      def run_test_case(cmd, xorg_keymap, test_case_name, inputs, expected):
+          with subtest(test_case_name):
+              assert len(inputs) == len(expected)
+              machine.execute("rm -f ${readyFile} ${resultFile}")
+
+              # set up process that expects all the keys to be entered
+              machine.succeed(
+                  "{} {} {} {} >&2 &".format(
+                      cmd,
+                      "${testReader}",
+                      len(inputs),
+                      shlex.quote("".join(expected)),
+                  )
+              )
+
+              if xorg_keymap:
+                  # make sure the xterm window is open and has focus
+                  machine.wait_for_window("testterm")
+                  machine.wait_until_succeeds(
+                      "${pkgs.xdotool}/bin/xdotool search --sync --onlyvisible "
+                      "--class testterm windowfocus --sync"
+                  )
+
+              # wait for reader to be ready
+              machine.wait_for_file("${readyFile}")
+
+              # send all keys
+              for key in inputs:
+                  machine.send_key(key)
+
+              # wait for result and check
+              machine.wait_for_file("${resultFile}")
+              machine.succeed("grep -q 'PASS:' ${resultFile}")
+
+
+      with open("${pkgs.writeText "tests.json" (builtins.toJSON tests)}") as json_file:
+          tests = json.load(json_file)
+
+      # These environments used to run in the opposite order, causing the
+      # following error at openvt startup.
+      #
+      # openvt: Couldn't deallocate console 1
+      #
+      # This error did not appear in successful runs.
+      # I don't know the exact cause, but I it seems that openvt and X are
+      # fighting over the virtual terminal. This does not appear to be a problem
+      # when the X test runs first.
+      keymap_environments = {
+          "Xorg Keymap": "DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e",
+          "VT Keymap": "openvt -sw --",
+      }
+
+      machine.wait_for_x()
+
+      for keymap_env_name, command in keymap_environments.items():
+          with subtest(keymap_env_name):
+              for test_case_name, test_data in tests.items():
+                  run_test_case(
+                      command,
+                      False,
+                      test_case_name,
+                      test_data["qwerty"],
+                      test_data["expect"],
+                  )
+    '';
+  };
+
+in pkgs.lib.mapAttrs mkKeyboardTest {
+  azerty = {
+    tests = {
+      azqw.qwerty = [ "q" "w" ];
+      azqw.expect = [ "a" "z" ];
+      altgr.qwerty = [ "alt_r-2" "alt_r-3" "alt_r-4" "alt_r-5" "alt_r-6" ];
+      altgr.expect = [ "~"       "#"       "{"       "["       "|"       ];
+    };
+
+    extraConfig.console.keyMap = "fr";
+    extraConfig.services.xserver.xkb.layout = "fr";
+  };
+
+  bone = {
+    tests = {
+      layer1.qwerty = [ "f"           "j"                     ];
+      layer1.expect = [ "e"           "n"                     ];
+      layer2.qwerty = [ "shift-f"     "shift-j"     "shift-6" ];
+      layer2.expect = [ "E"           "N"           "$"       ];
+      layer3.qwerty = [ "caps_lock-d" "caps_lock-f"           ];
+      layer3.expect = [ "{"           "}"                     ];
+    };
+
+    extraConfig.console.keyMap = "bone";
+    extraConfig.services.xserver.xkb.layout = "de";
+    extraConfig.services.xserver.xkb.variant = "bone";
+  };
+
+  colemak = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "r" "s" "t" "n" "e" "i" "o"         ];
+    };
+
+    extraConfig.console.keyMap = "colemak";
+    extraConfig.services.xserver.xkb.layout = "us";
+    extraConfig.services.xserver.xkb.variant = "colemak";
+  };
+
+  dvorak = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s"         ];
+      symbols.qwerty = [ "q" "w" "e" "minus" "equal" ];
+      symbols.expect = [ "'" "," "." "["     "]"     ];
+    };
+
+    extraConfig.console.keyMap = "dvorak";
+    extraConfig.services.xserver.xkb.layout = "us";
+    extraConfig.services.xserver.xkb.variant = "dvorak";
+  };
+
+  dvorak-programmer = {
+    tests = {
+      homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
+      homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s"         ];
+      numbers.qwerty = map (x: "shift-${x}")
+                       [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
+      numbers.expect = [ "%" "7" "5" "3" "1" "9" "0" "2" "4" "6" "8" ];
+      symbols.qwerty = [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
+      symbols.expect = [ "&" "[" "{" "}" "(" "=" "*" ")" "+" "]" "!" ];
+    };
+
+    extraConfig.console.keyMap = "dvorak-programmer";
+    extraConfig.services.xserver.xkb.layout = "us";
+    extraConfig.services.xserver.xkb.variant = "dvp";
+  };
+
+  neo = {
+    tests = {
+      layer1.qwerty = [ "f"           "j"                     ];
+      layer1.expect = [ "e"           "n"                     ];
+      layer2.qwerty = [ "shift-f"     "shift-j"     "shift-6" ];
+      layer2.expect = [ "E"           "N"           "$"       ];
+      layer3.qwerty = [ "caps_lock-d" "caps_lock-f"           ];
+      layer3.expect = [ "{"           "}"                     ];
+    };
+
+    extraConfig.console.keyMap = "neo";
+    extraConfig.services.xserver.xkb.layout = "de";
+    extraConfig.services.xserver.xkb.variant = "neo";
+  };
+
+  qwertz = {
+    tests = {
+      zy.qwerty = [ "z" "y" ];
+      zy.expect = [ "y" "z" ];
+      altgr.qwerty = map (x: "alt_r-${x}")
+                     [ "q" "less" "7" "8" "9" "0" ];
+      altgr.expect = [ "@" "|"    "{" "[" "]" "}" ];
+    };
+
+    extraConfig.console.keyMap = "de";
+    extraConfig.services.xserver.xkb.layout = "de";
+  };
+
+  custom = {
+    tests = {
+      us.qwerty = [ "a" "b" "g" "d" "z" "shift-2" "shift-3" ];
+      us.expect = [ "a" "b" "g" "d" "z" "@" "#" ];
+      greek.qwerty = map (x: "alt_r-${x}")
+                     [ "a" "b" "g" "d" "z" ];
+      greek.expect = [ "α" "β" "γ" "δ" "ζ" ];
+    };
+
+    extraConfig.console.useXkbConfig = true;
+    extraConfig.services.xserver.xkb.layout = "us-greek";
+    extraConfig.services.xserver.xkb.extraLayouts.us-greek =
+      { description = "US layout with alt-gr greek";
+        languages   = [ "eng" ];
+        symbolsFile = pkgs.writeText "us-greek" ''
+          xkb_symbols "us-greek"
+          {
+            include "us(basic)"
+            include "level3(ralt_switch)"
+            key <LatA> { [ a, A, Greek_alpha ] };
+            key <LatB> { [ b, B, Greek_beta  ] };
+            key <LatG> { [ g, G, Greek_gamma ] };
+            key <LatD> { [ d, D, Greek_delta ] };
+            key <LatZ> { [ z, Z, Greek_zeta  ] };
+          };
+        '';
+      };
+  };
+}
diff --git a/nixpkgs/nixos/tests/knot.nix b/nixpkgs/nixos/tests/knot.nix
new file mode 100644
index 000000000000..c5af8bf1edcc
--- /dev/null
+++ b/nixpkgs/nixos/tests/knot.nix
@@ -0,0 +1,203 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+let
+  common = {
+    networking.firewall.enable = false;
+    networking.useDHCP = false;
+  };
+  exampleZone = pkgs.writeTextDir "example.com.zone" ''
+      @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
+      @       NS      ns1
+      @       NS      ns2
+      ns1     A       192.168.0.1
+      ns1     AAAA    fd00::1
+      ns2     A       192.168.0.2
+      ns2     AAAA    fd00::2
+      www     A       192.0.2.1
+      www     AAAA    2001:DB8::1
+      sub     NS      ns.example.com.
+  '';
+  delegatedZone = pkgs.writeTextDir "sub.example.com.zone" ''
+      @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
+      @       NS      ns1.example.com.
+      @       NS      ns2.example.com.
+      @       A       192.0.2.2
+      @       AAAA    2001:DB8::2
+  '';
+
+  knotZonesEnv = pkgs.buildEnv {
+    name = "knot-zones";
+    paths = [ exampleZone delegatedZone ];
+  };
+  # DO NOT USE pkgs.writeText IN PRODUCTION. This put secrets in the nix store!
+  tsigFile = pkgs.writeText "tsig.conf" ''
+    key:
+      - id: xfr_key
+        algorithm: hmac-sha256
+        secret: zOYgOgnzx3TGe5J5I/0kxd7gTcxXhLYMEq3Ek3fY37s=
+  '';
+in {
+  name = "knot";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hexa ];
+  };
+
+
+  nodes = {
+    primary = { lib, ... }: {
+      imports = [ common ];
+
+      # trigger sched_setaffinity syscall
+      virtualisation.cores = 2;
+
+      networking.interfaces.eth1 = {
+        ipv4.addresses = lib.mkForce [
+          { address = "192.168.0.1"; prefixLength = 24; }
+        ];
+        ipv6.addresses = lib.mkForce [
+          { address = "fd00::1"; prefixLength = 64; }
+        ];
+      };
+      services.knot.enable = true;
+      services.knot.extraArgs = [ "-v" ];
+      services.knot.keyFiles = [ tsigFile ];
+      services.knot.settings = {
+        server = {
+          listen = [
+            "0.0.0.0@53"
+            "::@53"
+           ];
+          automatic-acl = true;
+        };
+
+        acl.secondary_acl = {
+          address = "192.168.0.2";
+          key = "xfr_key";
+          action = "transfer";
+        };
+
+        remote.secondary.address = "192.168.0.2@53";
+
+        template.default = {
+          storage = knotZonesEnv;
+          notify = [ "secondary" ];
+          acl = [ "secondary_acl" ];
+          dnssec-signing = true;
+          # Input-only zone files
+          # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
+          # prevents modification of the zonefiles, since the zonefiles are immutable
+          zonefile-sync = -1;
+          zonefile-load = "difference";
+          journal-content = "changes";
+        };
+
+        zone = {
+          "example.com".file = "example.com.zone";
+          "sub.example.com".file = "sub.example.com.zone";
+        };
+
+        log.syslog.any = "info";
+      };
+    };
+
+    secondary = { lib, ... }: {
+      imports = [ common ];
+      networking.interfaces.eth1 = {
+        ipv4.addresses = lib.mkForce [
+          { address = "192.168.0.2"; prefixLength = 24; }
+        ];
+        ipv6.addresses = lib.mkForce [
+          { address = "fd00::2"; prefixLength = 64; }
+        ];
+      };
+      services.knot.enable = true;
+      services.knot.keyFiles = [ tsigFile ];
+      services.knot.extraArgs = [ "-v" ];
+      services.knot.settings = {
+        server = {
+          automatic-acl = true;
+        };
+
+        xdp = {
+          listen = [
+            "eth1"
+          ];
+          tcp = true;
+        };
+
+        remote.primary = {
+          address = "192.168.0.1@53";
+          key = "xfr_key";
+        };
+
+        template.default = {
+          master = "primary";
+          # zonefileless setup
+          # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
+          zonefile-sync = "-1";
+          zonefile-load = "none";
+          journal-content = "all";
+        };
+
+        zone = {
+          "example.com".file = "example.com.zone";
+          "sub.example.com".file = "sub.example.com.zone";
+        };
+
+        log.syslog.any = "debug";
+      };
+    };
+    client = { lib, nodes, ... }: {
+      imports = [ common ];
+      networking.interfaces.eth1 = {
+        ipv4.addresses = [
+          { address = "192.168.0.3"; prefixLength = 24; }
+        ];
+        ipv6.addresses = [
+          { address = "fd00::3"; prefixLength = 64; }
+        ];
+      };
+      environment.systemPackages = [ pkgs.knot-dns ];
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    primary4 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv4.addresses).address;
+    primary6 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv6.addresses).address;
+
+    secondary4 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv4.addresses).address;
+    secondary6 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv6.addresses).address;
+  in ''
+    import re
+
+    start_all()
+
+    client.wait_for_unit("network.target")
+    primary.wait_for_unit("knot.service")
+    secondary.wait_for_unit("knot.service")
+
+
+    def test(host, query_type, query, pattern):
+        out = client.succeed(f"khost -t {query_type} {query} {host}").strip()
+        client.log(f"{host} replied with: {out}")
+        assert re.search(pattern, out), f'Did not match "{pattern}"'
+
+
+    for host in ("${primary4}", "${primary6}", "${secondary4}", "${secondary6}"):
+        with subtest(f"Interrogate {host}"):
+            test(host, "SOA", "example.com", r"start of authority.*noc\.example\.com\.")
+            test(host, "A", "example.com", r"has no [^ ]+ record")
+            test(host, "AAAA", "example.com", r"has no [^ ]+ record")
+
+            test(host, "A", "www.example.com", r"address 192.0.2.1$")
+            test(host, "AAAA", "www.example.com", r"address 2001:db8::1$")
+
+            test(host, "NS", "sub.example.com", r"nameserver is ns\d\.example\.com.$")
+            test(host, "A", "sub.example.com", r"address 192.0.2.2$")
+            test(host, "AAAA", "sub.example.com", r"address 2001:db8::2$")
+
+            test(host, "RRSIG", "www.example.com", r"RR set signature is")
+            test(host, "DNSKEY", "example.com", r"DNSSEC key is")
+
+    primary.log(primary.succeed("systemd-analyze security knot.service | grep -v '✓'"))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/komga.nix b/nixpkgs/nixos/tests/komga.nix
new file mode 100644
index 000000000000..d48d19bbbdd3
--- /dev/null
+++ b/nixpkgs/nixos/tests/komga.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "komga";
+  meta.maintainers = with lib.maintainers; [ govanify ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.komga = {
+        enable = true;
+        port = 1234;
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("komga.service")
+    machine.wait_for_open_port(1234)
+    machine.succeed("curl --fail http://localhost:1234/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/krb5/default.nix b/nixpkgs/nixos/tests/krb5/default.nix
new file mode 100644
index 000000000000..ede085632c63
--- /dev/null
+++ b/nixpkgs/nixos/tests/krb5/default.nix
@@ -0,0 +1,4 @@
+{ system ? builtins.currentSystem }:
+{
+  example-config = import ./example-config.nix { inherit system; };
+}
diff --git a/nixpkgs/nixos/tests/krb5/example-config.nix b/nixpkgs/nixos/tests/krb5/example-config.nix
new file mode 100644
index 000000000000..33bed481b39f
--- /dev/null
+++ b/nixpkgs/nixos/tests/krb5/example-config.nix
@@ -0,0 +1,118 @@
+# Verifies that the configuration suggested in (non-deprecated) example values
+# will result in the expected output.
+
+import ../make-test-python.nix ({ pkgs, ...} : {
+  name = "krb5-with-example-config";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eqyiel dblsaiko ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }: {
+      security.krb5 = {
+        enable = true;
+        package = pkgs.krb5;
+        settings = {
+          includedir = [
+            "/etc/krb5.conf.d"
+          ];
+          include = [
+            "/etc/krb5-extra.conf"
+          ];
+          libdefaults = {
+            default_realm = "ATHENA.MIT.EDU";
+          };
+          realms = {
+            "ATHENA.MIT.EDU" = {
+              admin_server = "athena.mit.edu";
+              kdc = [
+                "athena01.mit.edu"
+                "athena02.mit.edu"
+              ];
+            };
+          };
+          domain_realm = {
+            "example.com" = "EXAMPLE.COM";
+            ".example.com" = "EXAMPLE.COM";
+          };
+          capaths = {
+            "ATHENA.MIT.EDU" = {
+              "EXAMPLE.COM" = ".";
+            };
+            "EXAMPLE.COM" = {
+              "ATHENA.MIT.EDU" = ".";
+            };
+          };
+          appdefaults = {
+            pam = {
+              debug = false;
+              ticket_lifetime = 36000;
+              renew_lifetime = 36000;
+              max_timeout = 30;
+              timeout_shift = 2;
+              initial_timeout = 1;
+            };
+          };
+          plugins.ccselect.disable = "k5identity";
+          logging = {
+            kdc = "SYSLOG:NOTICE";
+            admin_server = "SYSLOG:NOTICE";
+            default = "SYSLOG:NOTICE";
+          };
+        };
+      };
+    };
+
+  testScript =
+    let snapshot = pkgs.writeText "krb5-with-example-config.conf" ''
+      [appdefaults]
+        pam = {
+          debug = false
+          initial_timeout = 1
+          max_timeout = 30
+          renew_lifetime = 36000
+          ticket_lifetime = 36000
+          timeout_shift = 2
+        }
+
+      [capaths]
+        ATHENA.MIT.EDU = {
+          EXAMPLE.COM = .
+        }
+        EXAMPLE.COM = {
+          ATHENA.MIT.EDU = .
+        }
+
+      [domain_realm]
+        .example.com = EXAMPLE.COM
+        example.com = EXAMPLE.COM
+
+      [libdefaults]
+        default_realm = ATHENA.MIT.EDU
+
+      [logging]
+        admin_server = SYSLOG:NOTICE
+        default = SYSLOG:NOTICE
+        kdc = SYSLOG:NOTICE
+
+      [plugins]
+        ccselect = {
+          disable = k5identity
+        }
+
+      [realms]
+        ATHENA.MIT.EDU = {
+          admin_server = athena.mit.edu
+          kdc = athena01.mit.edu
+          kdc = athena02.mit.edu
+        }
+
+      include /etc/krb5-extra.conf
+      includedir /etc/krb5.conf.d
+    '';
+  in ''
+    machine.succeed(
+        "diff /etc/krb5.conf ${snapshot}"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ksm.nix b/nixpkgs/nixos/tests/ksm.nix
new file mode 100644
index 000000000000..026d2ee85a24
--- /dev/null
+++ b/nixpkgs/nixos/tests/ksm.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ lib, ...} :
+
+{
+  name = "ksm";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    hardware.ksm.enable = true;
+    hardware.ksm.sleep = 300;
+  };
+
+  testScript =
+    ''
+      machine.start()
+      machine.wait_until_succeeds("test $(</sys/kernel/mm/ksm/run) -eq 1")
+      machine.wait_until_succeeds("test $(</sys/kernel/mm/ksm/sleep_millisecs) -eq 300")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/kthxbye.nix b/nixpkgs/nixos/tests/kthxbye.nix
new file mode 100644
index 000000000000..5ca0917ec8e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/kthxbye.nix
@@ -0,0 +1,110 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "kthxbye";
+
+  meta = with lib.maintainers; {
+    maintainers = [ nukaduka ];
+  };
+
+  nodes.server = { ... }: {
+    environment.systemPackages = with pkgs; [ prometheus-alertmanager ];
+    services.prometheus = {
+      enable = true;
+
+      globalConfig = {
+        scrape_interval = "5s";
+        scrape_timeout = "5s";
+        evaluation_interval = "5s";
+      };
+
+      scrapeConfigs = [
+        {
+          job_name = "prometheus";
+          scrape_interval = "5s";
+          static_configs = [
+            {
+              targets = [ "localhost:9090" ];
+            }
+          ];
+        }
+      ];
+
+      rules = [
+        ''
+          groups:
+            - name: test
+              rules:
+                - alert: node_up
+                  expr: up != 0
+                  for: 5s
+                  labels:
+                    severity: bottom of the barrel
+                  annotations:
+                    summary: node is fine
+        ''
+      ];
+
+      alertmanagers = [
+        {
+          static_configs = [
+            {
+              targets = [
+                "localhost:9093"
+              ];
+            }
+          ];
+        }
+      ];
+
+      alertmanager = {
+        enable = true;
+        openFirewall = true;
+        configuration.route = {
+          receiver = "test";
+          group_wait = "5s";
+          group_interval = "5s";
+          group_by = [ "..." ];
+        };
+        configuration.receivers = [
+          {
+            name = "test";
+            webhook_configs = [
+              {
+                url = "http://localhost:1234";
+              }
+            ];
+          }
+        ];
+      };
+    };
+
+    services.kthxbye = {
+      enable = true;
+      openFirewall = true;
+      extendIfExpiringIn = "30s";
+      logJSON = true;
+      maxDuration = "15m";
+      interval = "5s";
+    };
+  };
+
+  testScript = ''
+    with subtest("start the server"):
+      start_all()
+      server.wait_for_unit("prometheus.service")
+      server.wait_for_unit("alertmanager.service")
+      server.wait_for_unit("kthxbye.service")
+
+      server.sleep(2) # wait for units to settle
+      server.systemctl("restart kthxbye.service") # make sure kthxbye comes up after alertmanager
+      server.sleep(2)
+
+    with subtest("set up test silence which expires in 20s"):
+      server.succeed('amtool --alertmanager.url "http://localhost:9093" silence add alertname="node_up" -a "nixosTest" -d "20s" -c "ACK! this server is fine!!"')
+
+    with subtest("wait for 21 seconds and check if the silence is still active"):
+      server.sleep(21)
+      server.systemctl("status kthxbye.service")
+      server.succeed("amtool --alertmanager.url 'http://localhost:9093' silence | grep 'ACK'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/kubernetes/base.nix b/nixpkgs/nixos/tests/kubernetes/base.nix
new file mode 100644
index 000000000000..ba7b2d9b1d2d
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/base.nix
@@ -0,0 +1,107 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; }
+}:
+
+with import ../../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  mkKubernetesBaseTest =
+    { name, domain ? "my.zyx", test, machines
+    , extraConfiguration ? null }:
+    let
+      masterName = head (filter (machineName: any (role: role == "master") machines.${machineName}.roles) (attrNames machines));
+      master = machines.${masterName};
+      extraHosts = ''
+        ${master.ip}  etcd.${domain}
+        ${master.ip}  api.${domain}
+        ${concatMapStringsSep "\n" (machineName: "${machines.${machineName}.ip}  ${machineName}.${domain}") (attrNames machines)}
+      '';
+      wrapKubectl = with pkgs; runCommand "wrap-kubectl" { nativeBuildInputs = [ makeWrapper ]; } ''
+        mkdir -p $out/bin
+        makeWrapper ${pkgs.kubernetes}/bin/kubectl $out/bin/kubectl --set KUBECONFIG "/etc/kubernetes/cluster-admin.kubeconfig"
+      '';
+    in makeTest {
+      inherit name;
+
+      nodes = mapAttrs (machineName: machine:
+        { config, pkgs, lib, nodes, ... }:
+          mkMerge [
+            {
+              boot.postBootCommands = "rm -fr /var/lib/kubernetes/secrets /tmp/shared/*";
+              virtualisation.memorySize = mkDefault 1536;
+              virtualisation.diskSize = mkDefault 4096;
+              networking = {
+                inherit domain extraHosts;
+                primaryIPAddress = mkForce machine.ip;
+
+                firewall = {
+                  allowedTCPPorts = [
+                    10250 # kubelet
+                  ];
+                  trustedInterfaces = ["mynet"];
+
+                  extraCommands = concatMapStrings  (node: ''
+                    iptables -A INPUT -s ${node.networking.primaryIPAddress} -j ACCEPT
+                  '') (attrValues nodes);
+                };
+              };
+              programs.bash.enableCompletion = true;
+              environment.systemPackages = [ wrapKubectl ];
+              services.flannel.iface = "eth1";
+              services.kubernetes = {
+                proxy.hostname = "${masterName}.${domain}";
+
+                easyCerts = true;
+                inherit (machine) roles;
+                apiserver = {
+                  securePort = 443;
+                  advertiseAddress = master.ip;
+                };
+                masterAddress = "${masterName}.${config.networking.domain}";
+              };
+            }
+            (optionalAttrs (any (role: role == "master") machine.roles) {
+              networking.firewall.allowedTCPPorts = [
+                443 # kubernetes apiserver
+              ];
+            })
+            (optionalAttrs (machine ? extraConfiguration) (machine.extraConfiguration { inherit config pkgs lib nodes; }))
+            (optionalAttrs (extraConfiguration != null) (extraConfiguration { inherit config pkgs lib nodes; }))
+          ]
+      ) machines;
+
+      testScript = ''
+        start_all()
+      '' + test;
+    };
+
+  mkKubernetesMultiNodeTest = attrs: mkKubernetesBaseTest ({
+    machines = {
+      machine1 = {
+        roles = ["master"];
+        ip = "192.168.1.1";
+      };
+      machine2 = {
+        roles = ["node"];
+        ip = "192.168.1.2";
+      };
+    };
+  } // attrs // {
+    name = "kubernetes-${attrs.name}-multinode";
+  });
+
+  mkKubernetesSingleNodeTest = attrs: mkKubernetesBaseTest ({
+    machines = {
+      machine1 = {
+        roles = ["master" "node"];
+        ip = "192.168.1.1";
+      };
+    };
+  } // attrs // {
+    name = "kubernetes-${attrs.name}-singlenode";
+  });
+in {
+  inherit mkKubernetesBaseTest mkKubernetesSingleNodeTest mkKubernetesMultiNodeTest;
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/default.nix b/nixpkgs/nixos/tests/kubernetes/default.nix
new file mode 100644
index 000000000000..a3de9ed115d4
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/default.nix
@@ -0,0 +1,13 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+let
+  dns = import ./dns.nix { inherit system pkgs; };
+  rbac = import ./rbac.nix { inherit system pkgs; };
+in
+{
+  dns-single-node = dns.singlenode.test;
+  dns-multi-node = dns.multinode.test;
+  rbac-single-node = rbac.singlenode.test;
+  rbac-multi-node = rbac.multinode.test;
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/dns.nix b/nixpkgs/nixos/tests/kubernetes/dns.nix
new file mode 100644
index 000000000000..1b7145eb5d5e
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/dns.nix
@@ -0,0 +1,159 @@
+{ system ? builtins.currentSystem, pkgs ? import ../../.. { inherit system; } }:
+with import ./base.nix { inherit system; };
+let
+  domain = "my.zyx";
+
+  redisPod = pkgs.writeText "redis-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "redis";
+    metadata.labels.name = "redis";
+    spec.containers = [{
+      name = "redis";
+      image = "redis";
+      args = ["--bind" "0.0.0.0"];
+      imagePullPolicy = "Never";
+      ports = [{
+        name = "redis-server";
+        containerPort = 6379;
+      }];
+    }];
+  });
+
+  redisService = pkgs.writeText "redis-service.json" (builtins.toJSON {
+    kind = "Service";
+    apiVersion = "v1";
+    metadata.name = "redis";
+    spec = {
+      ports = [{port = 6379; targetPort = 6379;}];
+      selector = {name = "redis";};
+    };
+  });
+
+  redisImage = pkgs.dockerTools.buildImage {
+    name = "redis";
+    tag = "latest";
+    copyToRoot = pkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ pkgs.redis pkgs.bind.host ];
+    };
+    config.Entrypoint = ["/bin/redis-server"];
+  };
+
+  probePod = pkgs.writeText "probe-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "probe";
+    metadata.labels.name = "probe";
+    spec.containers = [{
+      name = "probe";
+      image = "probe";
+      args = [ "-f" ];
+      tty = true;
+      imagePullPolicy = "Never";
+    }];
+  });
+
+  probeImage = pkgs.dockerTools.buildImage {
+    name = "probe";
+    tag = "latest";
+    copyToRoot = pkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ pkgs.bind.host pkgs.busybox ];
+    };
+    config.Entrypoint = ["/bin/tail"];
+  };
+
+  extraConfiguration = { config, pkgs, lib, ... }: {
+    environment.systemPackages = [ pkgs.bind.host ];
+    services.dnsmasq.enable = true;
+    services.dnsmasq.settings.server = [
+      "/cluster.local/${config.services.kubernetes.addons.dns.clusterIp}#53"
+    ];
+  };
+
+  base = {
+    name = "dns";
+    inherit domain extraConfiguration;
+  };
+
+  singleNodeTest = {
+    test = ''
+      # prepare machine1 for test
+      machine1.wait_until_succeeds("kubectl get node machine1.${domain} | grep -w Ready")
+      machine1.wait_until_succeeds(
+          "${pkgs.gzip}/bin/zcat ${redisImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${redisPod}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${redisService}"
+      )
+      machine1.wait_until_succeeds(
+          "${pkgs.gzip}/bin/zcat ${probeImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${probePod}"
+      )
+
+      # check if pods are running
+      machine1.wait_until_succeeds("kubectl get pod redis | grep Running")
+      machine1.wait_until_succeeds("kubectl get pod probe | grep Running")
+      machine1.wait_until_succeeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'")
+
+      # check dns on host (dnsmasq)
+      machine1.succeed("host redis.default.svc.cluster.local")
+
+      # check dns inside the container
+      machine1.succeed("kubectl exec probe -- /bin/host redis.default.svc.cluster.local")
+    '';
+  };
+
+  multiNodeTest = {
+    test = ''
+      # Node token exchange
+      machine1.wait_until_succeeds(
+          "cp -f /var/lib/cfssl/apitoken.secret /tmp/shared/apitoken.secret"
+      )
+      machine2.wait_until_succeeds(
+          "cat /tmp/shared/apitoken.secret | nixos-kubernetes-node-join"
+      )
+
+      # prepare machines for test
+      machine1.wait_until_succeeds("kubectl get node machine2.${domain} | grep -w Ready")
+      machine2.wait_until_succeeds(
+          "${pkgs.gzip}/bin/zcat ${redisImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${redisPod}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${redisService}"
+      )
+      machine2.wait_until_succeeds(
+          "${pkgs.gzip}/bin/zcat ${probeImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${probePod}"
+      )
+
+      # check if pods are running
+      machine1.wait_until_succeeds("kubectl get pod redis | grep Running")
+      machine1.wait_until_succeeds("kubectl get pod probe | grep Running")
+      machine1.wait_until_succeeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'")
+
+      # check dns on hosts (dnsmasq)
+      machine1.succeed("host redis.default.svc.cluster.local")
+      machine2.succeed("host redis.default.svc.cluster.local")
+
+      # check dns inside the container
+      machine1.succeed("kubectl exec probe -- /bin/host redis.default.svc.cluster.local")
+    '';
+  };
+in {
+  singlenode = mkKubernetesSingleNodeTest (base // singleNodeTest);
+  multinode = mkKubernetesMultiNodeTest (base // multiNodeTest);
+}
diff --git a/nixpkgs/nixos/tests/kubernetes/rbac.nix b/nixpkgs/nixos/tests/kubernetes/rbac.nix
new file mode 100644
index 000000000000..779eafbb1d24
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubernetes/rbac.nix
@@ -0,0 +1,168 @@
+{ system ? builtins.currentSystem, pkgs ? import ../../.. { inherit system; } }:
+with import ./base.nix { inherit system; };
+let
+
+  roServiceAccount = pkgs.writeText "ro-service-account.json" (builtins.toJSON {
+    kind = "ServiceAccount";
+    apiVersion = "v1";
+    metadata = {
+      name = "read-only";
+      namespace = "default";
+    };
+  });
+
+  roRoleBinding = pkgs.writeText "ro-role-binding.json" (builtins.toJSON {
+    apiVersion = "rbac.authorization.k8s.io/v1";
+    kind = "RoleBinding";
+    metadata = {
+      name = "read-pods";
+      namespace = "default";
+    };
+    roleRef = {
+      apiGroup = "rbac.authorization.k8s.io";
+      kind = "Role";
+      name = "pod-reader";
+    };
+    subjects = [{
+      kind = "ServiceAccount";
+      name = "read-only";
+      namespace = "default";
+    }];
+  });
+
+  roRole = pkgs.writeText "ro-role.json" (builtins.toJSON {
+    apiVersion = "rbac.authorization.k8s.io/v1";
+    kind = "Role";
+    metadata = {
+      name = "pod-reader";
+      namespace = "default";
+    };
+    rules = [{
+      apiGroups = [""];
+      resources = ["pods"];
+      verbs = ["get" "list" "watch"];
+    }];
+  });
+
+  kubectlPod = pkgs.writeText "kubectl-pod.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "kubectl";
+    metadata.namespace = "default";
+    metadata.labels.name = "kubectl";
+    spec.serviceAccountName = "read-only";
+    spec.containers = [{
+      name = "kubectl";
+      image = "kubectl:latest";
+      command = ["/bin/tail" "-f"];
+      imagePullPolicy = "Never";
+      tty = true;
+    }];
+  });
+
+  kubectlPod2 = pkgs.writeTextDir "kubectl-pod-2.json" (builtins.toJSON {
+    kind = "Pod";
+    apiVersion = "v1";
+    metadata.name = "kubectl-2";
+    metadata.namespace = "default";
+    metadata.labels.name = "kubectl-2";
+    spec.serviceAccountName = "read-only";
+    spec.containers = [{
+      name = "kubectl-2";
+      image = "kubectl:latest";
+      command = ["/bin/tail" "-f"];
+      imagePullPolicy = "Never";
+      tty = true;
+    }];
+  });
+
+  copyKubectl = pkgs.runCommand "copy-kubectl" { } ''
+    mkdir -p $out/bin
+    cp ${pkgs.kubernetes}/bin/kubectl $out/bin/kubectl
+  '';
+
+  kubectlImage = pkgs.dockerTools.buildImage {
+    name = "kubectl";
+    tag = "latest";
+    copyToRoot = pkgs.buildEnv {
+      name = "image-root";
+      pathsToLink = [ "/bin" ];
+      paths = [ copyKubectl pkgs.busybox kubectlPod2 ];
+    };
+    config.Entrypoint = ["/bin/sh"];
+  };
+
+  base = {
+    name = "rbac";
+  };
+
+  singlenode = base // {
+    test = ''
+      machine1.wait_until_succeeds("kubectl get node machine1.my.zyx | grep -w Ready")
+
+      machine1.wait_until_succeeds(
+          "${pkgs.gzip}/bin/zcat ${kubectlImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
+      )
+
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roServiceAccount}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roRole}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roRoleBinding}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${kubectlPod}"
+      )
+
+      machine1.wait_until_succeeds("kubectl get pod kubectl | grep Running")
+
+      machine1.wait_until_succeeds("kubectl exec kubectl -- kubectl get pods")
+      machine1.fail("kubectl exec kubectl -- kubectl create -f /kubectl-pod-2.json")
+      machine1.fail("kubectl exec kubectl -- kubectl delete pods -l name=kubectl")
+    '';
+  };
+
+  multinode = base // {
+    test = ''
+      # Node token exchange
+      machine1.wait_until_succeeds(
+          "cp -f /var/lib/cfssl/apitoken.secret /tmp/shared/apitoken.secret"
+      )
+      machine2.wait_until_succeeds(
+          "cat /tmp/shared/apitoken.secret | nixos-kubernetes-node-join"
+      )
+
+      machine1.wait_until_succeeds("kubectl get node machine2.my.zyx | grep -w Ready")
+
+      machine2.wait_until_succeeds(
+          "${pkgs.gzip}/bin/zcat ${kubectlImage} | ${pkgs.containerd}/bin/ctr -n k8s.io image import -"
+      )
+
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roServiceAccount}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roRole}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl apply -f ${roRoleBinding}"
+      )
+      machine1.wait_until_succeeds(
+          "kubectl create -f ${kubectlPod}"
+      )
+
+      machine1.wait_until_succeeds("kubectl get pod kubectl | grep Running")
+
+      machine1.wait_until_succeeds("kubectl exec kubectl -- kubectl get pods")
+      machine1.fail("kubectl exec kubectl -- kubectl create -f /kubectl-pod-2.json")
+      machine1.fail("kubectl exec kubectl -- kubectl delete pods -l name=kubectl")
+    '';
+  };
+
+in {
+  singlenode = mkKubernetesSingleNodeTest singlenode;
+  multinode = mkKubernetesMultiNodeTest multinode;
+}
diff --git a/nixpkgs/nixos/tests/kubo/default.nix b/nixpkgs/nixos/tests/kubo/default.nix
new file mode 100644
index 000000000000..d8c0c69dc1fb
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubo/default.nix
@@ -0,0 +1,7 @@
+{ recurseIntoAttrs, runTest }:
+recurseIntoAttrs {
+  kubo = runTest ./kubo.nix;
+  # The FUSE functionality is completely broken since Kubo v0.24.0
+  # See https://github.com/ipfs/kubo/issues/10242
+  # kubo-fuse = runTest ./kubo-fuse.nix;
+}
diff --git a/nixpkgs/nixos/tests/kubo/kubo-fuse.nix b/nixpkgs/nixos/tests/kubo/kubo-fuse.nix
new file mode 100644
index 000000000000..71a5bf61649f
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubo/kubo-fuse.nix
@@ -0,0 +1,42 @@
+{ lib, ...} : {
+  name = "kubo-fuse";
+  meta = with lib.maintainers; {
+    maintainers = [ mguentner Luflosi ];
+  };
+
+  nodes.machine = { config, ... }: {
+    services.kubo = {
+      enable = true;
+      autoMount = true;
+    };
+    users.users.alice = {
+      isNormalUser = true;
+      extraGroups = [ config.services.kubo.group ];
+    };
+    users.users.bob = {
+      isNormalUser = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("FUSE mountpoint"):
+        machine.fail("echo a | su bob -l -c 'ipfs add --quieter'")
+        # The FUSE mount functionality is broken as of v0.13.0 and v0.17.0.
+        # See https://github.com/ipfs/kubo/issues/9044.
+        # Workaround: using CID Version 1 avoids that.
+        ipfs_hash = machine.succeed(
+            "echo fnord3 | su alice -l -c 'ipfs add --quieter --cid-version=1'"
+        ).strip()
+
+        machine.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")
+
+    with subtest("Unmounting of /ipns and /ipfs"):
+        # Force Kubo to crash and wait for it to restart
+        machine.systemctl("kill --signal=SIGKILL ipfs.service")
+        machine.wait_for_unit("ipfs.service", timeout = 30)
+
+        machine.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/kubo/kubo.nix b/nixpkgs/nixos/tests/kubo/kubo.nix
new file mode 100644
index 000000000000..b8222c652b33
--- /dev/null
+++ b/nixpkgs/nixos/tests/kubo/kubo.nix
@@ -0,0 +1,60 @@
+{ lib, ...} : {
+  name = "kubo";
+  meta = with lib.maintainers; {
+    maintainers = [ mguentner Luflosi ];
+  };
+
+  nodes.machine = { config, ... }: {
+    services.kubo = {
+      enable = true;
+      # Also will add a unix domain socket socket API address, see module.
+      startWhenNeeded = true;
+      settings.Addresses.API = "/ip4/127.0.0.1/tcp/2324";
+      dataDir = "/mnt/ipfs";
+    };
+    users.users.alice = {
+      isNormalUser = true;
+      extraGroups = [ config.services.kubo.group ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("Automatic socket activation"):
+        ipfs_hash = machine.succeed(
+            "echo fnord0 | su alice -l -c 'ipfs add --quieter'"
+        )
+        machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord0")
+
+    machine.stop_job("ipfs")
+
+    with subtest("IPv4 socket activation"):
+        machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id")
+        ipfs_hash = machine.succeed(
+            "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter"
+        )
+        machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord")
+
+    machine.stop_job("ipfs")
+
+    with subtest("Unix domain socket activation"):
+        ipfs_hash = machine.succeed(
+            "echo fnord2 | ipfs --api /unix/run/ipfs.sock add --quieter"
+        )
+        machine.succeed(
+            f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2"
+        )
+
+    machine.stop_job("ipfs")
+
+    with subtest("Socket activation for the Gateway"):
+        machine.succeed(
+            f"curl 'http://127.0.0.1:8080/ipfs/{ipfs_hash.strip()}' | grep fnord2"
+        )
+
+    with subtest("Setting dataDir works properly with the hardened systemd unit"):
+        machine.succeed("test -e /mnt/ipfs/config")
+        machine.succeed("test ! -e /var/lib/ipfs/")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/ladybird.nix b/nixpkgs/nixos/tests/ladybird.nix
new file mode 100644
index 000000000000..4e9ab9a36d13
--- /dev/null
+++ b/nixpkgs/nixos/tests/ladybird.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "ladybird";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [
+      pkgs.ladybird
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.succeed("echo '<!DOCTYPE html><html><body><h1>Hello world</h1></body></html>' > page.html")
+      machine.execute("ladybird file://$(pwd)/page.html >&2 &")
+      machine.wait_for_window("Ladybird")
+      machine.sleep(5)
+      machine.wait_for_text("Hello world")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/languagetool.nix b/nixpkgs/nixos/tests/languagetool.nix
new file mode 100644
index 000000000000..e4ab2a47064e
--- /dev/null
+++ b/nixpkgs/nixos/tests/languagetool.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let port = 8082;
+in {
+  name = "languagetool";
+  meta = with lib.maintainers; { maintainers = [ fbeffa ]; };
+
+  nodes.machine = { ... }:
+    {
+      services.languagetool.enable = true;
+      services.languagetool.port = port;
+    };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("languagetool.service")
+    machine.wait_for_open_port(${toString port})
+    machine.wait_until_succeeds('curl -d "language=en-US" -d "text=a simple test" http://localhost:${toString port}/v2/check')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lanraragi.nix b/nixpkgs/nixos/tests/lanraragi.nix
new file mode 100644
index 000000000000..7a4a1a489bdf
--- /dev/null
+++ b/nixpkgs/nixos/tests/lanraragi.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lanraragi";
+  meta.maintainers = with lib.maintainers; [ tomasajt ];
+
+  nodes = {
+    machine1 = { pkgs, ... }: {
+      services.lanraragi.enable = true;
+    };
+    machine2 = { pkgs, ... }: {
+      services.lanraragi = {
+        enable = true;
+        passwordFile = pkgs.writeText "lrr-test-pass" ''
+          Ultra-secure-p@ssword-"with-spec1al\chars
+        '';
+        port = 4000;
+        redis = {
+          port = 4001;
+          passwordFile = pkgs.writeText "redis-lrr-test-pass" ''
+            123-redis-PASS
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine1.wait_for_unit("lanraragi.service")
+    machine1.wait_until_succeeds("curl -f localhost:3000")
+    machine1.succeed("[ $(curl -o /dev/null -X post 'http://localhost:3000/login' --data-raw 'password=kamimamita' -w '%{http_code}') -eq 302 ]")
+
+    machine2.wait_for_unit("lanraragi.service")
+    machine2.wait_until_succeeds("curl -f localhost:4000")
+    machine2.succeed("[ $(curl -o /dev/null -X post 'http://localhost:4000/login' --data-raw 'password=Ultra-secure-p@ssword-\"with-spec1al\\chars' -w '%{http_code}') -eq 302 ]")
+  '';
+})
+
diff --git a/nixpkgs/nixos/tests/leaps.nix b/nixpkgs/nixos/tests/leaps.nix
new file mode 100644
index 000000000000..5cc387c86a45
--- /dev/null
+++ b/nixpkgs/nixos/tests/leaps.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs,  ... }:
+
+{
+  name = "leaps";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ qknight ];
+  };
+
+  nodes =
+    {
+      client = { };
+
+      server =
+        { services.leaps = {
+            enable = true;
+            port = 6666;
+            path = "/leaps/";
+          };
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript =
+    ''
+      start_all()
+      server.wait_for_open_port(6666)
+      client.wait_for_unit("network.target")
+      assert "leaps" in client.succeed(
+          "${pkgs.curl}/bin/curl -f http://server:6666/leaps/"
+      )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/legit.nix b/nixpkgs/nixos/tests/legit.nix
new file mode 100644
index 000000000000..a71fb1743c76
--- /dev/null
+++ b/nixpkgs/nixos/tests/legit.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+let
+  port = 5000;
+  scanPath = "/var/lib/legit";
+in
+{
+  name = "legit-web";
+  meta.maintainers = [ lib.maintainers.ratsclub ];
+
+  nodes = {
+    server = { config, pkgs, ... }: {
+      services.legit = {
+        enable = true;
+        settings = {
+          server.port = 5000;
+          repo = { inherit scanPath; };
+        };
+      };
+
+      environment.systemPackages = [ pkgs.git ];
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      strPort = builtins.toString port;
+    in
+    ''
+      start_all()
+
+      server.wait_for_unit("network.target")
+      server.wait_for_unit("legit.service")
+
+      server.wait_until_succeeds(
+          "curl -f http://localhost:${strPort}"
+      )
+
+      server.succeed("${pkgs.writeShellScript "setup-legit-test-repo" ''
+        set -e
+        git init --bare -b master ${scanPath}/some-repo
+        git init -b master reference
+        cd reference
+        git remote add origin ${scanPath}/some-repo
+        date > date.txt
+        git add date.txt
+        git -c user.name=test -c user.email=test@localhost commit -m 'add date'
+        git push -u origin master
+      ''}")
+
+      server.wait_until_succeeds(
+          "curl -f http://localhost:${strPort}/some-repo"
+      )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/lemmy.nix b/nixpkgs/nixos/tests/lemmy.nix
new file mode 100644
index 000000000000..d93df3646837
--- /dev/null
+++ b/nixpkgs/nixos/tests/lemmy.nix
@@ -0,0 +1,100 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  uiPort = 1234;
+  backendPort = 5678;
+  lemmyNodeName = "server";
+in
+{
+  name = "lemmy";
+  meta = with lib.maintainers; { maintainers = [ mightyiam ]; };
+
+  nodes = {
+    client = { };
+
+    "${lemmyNodeName}" = {
+      services.lemmy = {
+        enable = true;
+        ui.port = uiPort;
+        database.createLocally = true;
+        settings = {
+          hostname = "http://${lemmyNodeName}";
+          port = backendPort;
+          # Without setup, the /feeds/* and /nodeinfo/* API endpoints won't return 200
+          setup = {
+            admin_username = "mightyiam";
+            site_name = "Lemmy FTW";
+            admin_email = "mightyiam@example.com";
+          };
+        };
+        adminPasswordFile = /etc/lemmy-admin-password.txt;
+        caddy.enable = true;
+      };
+
+      environment.etc."lemmy-admin-password.txt".text = "ThisIsWhatIUseEverywhereTryIt";
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      # pict-rs seems to need more than 1025114112 bytes
+      virtualisation.memorySize = 2000;
+    };
+  };
+
+  testScript = ''
+    server = ${lemmyNodeName}
+
+    with subtest("the merged config is secure"):
+        server.wait_for_unit("lemmy.service")
+        config_permissions = server.succeed("stat --format %A /run/lemmy/config.hjson").rstrip()
+        assert config_permissions == "-rw-------", f"merged config permissions {config_permissions} are insecure"
+        directory_permissions = server.succeed("stat --format %A /run/lemmy").rstrip()
+        assert directory_permissions[5] == directory_permissions[8] == "-", "merged config can be replaced"
+
+    with subtest("the backend starts and responds"):
+        server.wait_for_open_port(${toString backendPort})
+        # wait until succeeds, it just needs few seconds for migrations, but lets give it 10s max
+        server.wait_until_succeeds("curl --fail localhost:${toString backendPort}/api/v3/site", 10)
+
+    with subtest("the UI starts and responds"):
+        server.wait_for_unit("lemmy-ui.service")
+        server.wait_for_open_port(${toString uiPort})
+        server.succeed("curl --fail localhost:${toString uiPort}")
+
+    with subtest("Lemmy-UI responds through the caddy reverse proxy"):
+        server.systemctl("start network-online.target")
+        server.wait_for_unit("network-online.target")
+        server.wait_for_unit("caddy.service")
+        server.wait_for_open_port(80)
+        body = server.execute("curl --fail --location ${lemmyNodeName}")[1]
+        assert "Lemmy" in body, f"String Lemmy not found in response for ${lemmyNodeName}: \n{body}"
+
+    with subtest("the server is exposed externally"):
+        client.systemctl("start network-online.target")
+        client.wait_for_unit("network-online.target")
+        client.succeed("curl -v --fail ${lemmyNodeName}")
+
+    with subtest("caddy correctly routes backend requests"):
+        # Make sure we are not hitting frontend
+        server.execute("systemctl stop lemmy-ui.service")
+
+        def assert_http_code(url, expected_http_code, extra_curl_args=""):
+            _, http_code = server.execute(f'curl --silent -o /dev/null {extra_curl_args} --fail --write-out "%{{http_code}}" {url}')
+            assert http_code == str(expected_http_code), f"expected http code {expected_http_code}, got {http_code}"
+
+        # Caddy responds with HTTP code 502 if it cannot handle the requested path
+        assert_http_code("${lemmyNodeName}/obviously-wrong-path/", 502)
+
+        assert_http_code("${lemmyNodeName}/static/js/client.js", 200)
+        assert_http_code("${lemmyNodeName}/api/v3/site", 200)
+
+        # A 404 confirms that the request goes to the backend
+        # No path can return 200 until after we upload an image to pict-rs
+        assert_http_code("${lemmyNodeName}/pictrs/", 404)
+
+        assert_http_code("${lemmyNodeName}/feeds/all.xml", 200)
+        assert_http_code("${lemmyNodeName}/nodeinfo/2.0.json", 200)
+
+        assert_http_code("${lemmyNodeName}/some-other-made-up-path/", 404, "-X POST")
+        assert_http_code("${lemmyNodeName}/some-other-path", 404, "-H 'Accept: application/activity+json'")
+        assert_http_code("${lemmyNodeName}/some-other-path", 404, "-H 'Accept: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/libinput.nix b/nixpkgs/nixos/tests/libinput.nix
new file mode 100644
index 000000000000..9b6fa159b999
--- /dev/null
+++ b/nixpkgs/nixos/tests/libinput.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ ... }:
+
+{
+  name = "libinput";
+
+  nodes.machine = { ... }:
+    {
+      imports = [
+        ./common/x11.nix
+        ./common/user-account.nix
+      ];
+
+      test-support.displayManager.auto.user = "alice";
+
+      services.xserver.libinput = {
+        enable = true;
+        mouse = {
+          naturalScrolling = true;
+          leftHanded = true;
+          middleEmulation = false;
+          horizontalScrolling = false;
+        };
+      };
+    };
+
+  testScript = ''
+    def expect_xserver_option(option, value):
+        machine.succeed(f"""cat /var/log/X.0.log | grep -F 'Option "{option}" "{value}"'""")
+
+    machine.start()
+    machine.wait_for_x()
+    machine.succeed("""cat /var/log/X.0.log | grep -F "Using input driver 'libinput'" """)
+    expect_xserver_option("NaturalScrolling", "on")
+    expect_xserver_option("LeftHanded", "on")
+    expect_xserver_option("MiddleEmulation", "off")
+    expect_xserver_option("HorizontalScrolling", "off")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/libreddit.nix b/nixpkgs/nixos/tests/libreddit.nix
new file mode 100644
index 000000000000..ecf347b9e12e
--- /dev/null
+++ b/nixpkgs/nixos/tests/libreddit.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "libreddit";
+  meta.maintainers = with lib.maintainers; [ fab ];
+
+  nodes.machine = {
+    services.libreddit.enable = true;
+    # Test CAP_NET_BIND_SERVICE
+    services.libreddit.port = 80;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("libreddit.service")
+    machine.wait_for_open_port(80)
+    # Query a page that does not require Internet access
+    machine.succeed("curl --fail http://localhost:80/settings")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/librenms.nix b/nixpkgs/nixos/tests/librenms.nix
new file mode 100644
index 000000000000..c59f56a32316
--- /dev/null
+++ b/nixpkgs/nixos/tests/librenms.nix
@@ -0,0 +1,108 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  api_token = "f87f42114e44b63ad1b9e3c3d33d6fbe"; # random md5 hash
+  wrong_api_token = "e68ba041fcf1eab923a7a6de3af5f726"; # another random md5 hash
+in {
+  name = "librenms";
+  meta.maintainers = lib.teams.wdz.members;
+
+  nodes.librenms = {
+    time.timeZone = "Europe/Berlin";
+
+    environment.systemPackages = with pkgs; [
+      curl
+      jq
+    ];
+
+    services.librenms = {
+      enable = true;
+      hostname = "librenms";
+      database = {
+        createLocally = true;
+        host = "localhost";
+        database = "librenms";
+        username = "librenms";
+        passwordFile = pkgs.writeText "librenms-db-pass" "librenmsdbpass";
+      };
+      nginx = {
+        default = true;
+      };
+      enableOneMinutePolling = true;
+      settings = {
+        enable_billing = true;
+      };
+    };
+
+    # systemd oneshot to create a dummy admin user and a API token for testing
+    systemd.services.lnms-api-init = {
+      description = "LibreNMS API init";
+      after = [ "librenms-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        User = "root";
+        Group = "root";
+      };
+      script = ''
+        API_USER_NAME=api
+        API_TOKEN=${api_token} # random md5 hash
+
+        # we don't need to know the password, it just has to exist
+        API_USER_PASS=$(${pkgs.pwgen}/bin/pwgen -s 64 1)
+        ${pkgs.librenms}/artisan user:add $API_USER_NAME -r admin -p $API_USER_PASS
+        API_USER_ID=$(${pkgs.mariadb}/bin/mysql -D librenms -N -B -e "SELECT user_id FROM users WHERE username = '$API_USER_NAME';")
+
+        ${pkgs.mariadb}/bin/mysql -D librenms -e "INSERT INTO api_tokens (user_id, token_hash, description) VALUES ($API_USER_ID, '$API_TOKEN', 'API User')"
+      '';
+    };
+  };
+
+  nodes.snmphost = {
+    networking.firewall.allowedUDPPorts = [ 161 ];
+
+    systemd.services.snmpd = {
+      description = "snmpd";
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "forking";
+        User = "root";
+        Group = "root";
+        ExecStart = let
+          snmpd-config = pkgs.writeText "snmpd-config" ''
+            com2sec readonly default public
+
+            group MyROGroup v2c        readonly
+            view all    included  .1                               80
+            access MyROGroup ""      any       noauth    exact  all    none   none
+
+            syslocation Testcity, Testcountry
+            syscontact Testi mc Test <test@example.com>
+          '';
+        in "${pkgs.net-snmp}/bin/snmpd -c ${snmpd-config} -C";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    snmphost.wait_until_succeeds("pgrep snmpd")
+
+    librenms.wait_for_unit("lnms-api-init.service")
+    librenms.wait_for_open_port(80)
+
+    # Test that we can authenticate against the API
+    librenms.succeed("curl --fail -H 'X-Auth-Token: ${api_token}' http://localhost/api/v0")
+    librenms.fail("curl --fail -H 'X-Auth-Token: ${wrong_api_token}' http://localhost/api/v0")
+
+    # add snmphost as a device
+    librenms.succeed("curl --fail -X POST -d '{\"hostname\":\"snmphost\",\"version\":\"v2c\",\"community\":\"public\"}' -H 'X-Auth-Token: ${api_token}' http://localhost/api/v0/devices")
+
+    # wait until snmphost gets polled
+    librenms.wait_until_succeeds("test $(curl -H 'X-Auth-Token: ${api_token}' http://localhost/api/v0/devices/snmphost | jq -Mr .devices[0].last_polled) != 'null'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/libresprite.nix b/nixpkgs/nixos/tests/libresprite.nix
new file mode 100644
index 000000000000..16d272acfa0f
--- /dev/null
+++ b/nixpkgs/nixos/tests/libresprite.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "libresprite";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [
+      pkgs.imagemagick
+      pkgs.libresprite
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.succeed("convert -font DejaVu-Sans +antialias label:'IT WORKS' image.png")
+      machine.execute("libresprite image.png >&2 &")
+      machine.wait_for_window("LibreSprite v${pkgs.libresprite.version}")
+      machine.wait_for_text("IT WORKS")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/libreswan.nix b/nixpkgs/nixos/tests/libreswan.nix
new file mode 100644
index 000000000000..aadba941fab1
--- /dev/null
+++ b/nixpkgs/nixos/tests/libreswan.nix
@@ -0,0 +1,136 @@
+# This test sets up a host-to-host IPsec VPN between Alice and Bob, each on its
+# own network and with Eve as the only route between each other. We check that
+# Eve can eavesdrop the plaintext traffic between Alice and Bob, but once they
+# enable the secure tunnel Eve's spying becomes ineffective.
+
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+
+  # IPsec tunnel between Alice and Bob
+  tunnelConfig = {
+    services.libreswan.enable = true;
+    services.libreswan.connections.tunnel =
+      ''
+        leftid=@alice
+        left=fd::a
+        rightid=@bob
+        right=fd::b
+        authby=secret
+        auto=add
+      '';
+    environment.etc."ipsec.d/tunnel.secrets" =
+      { text = ''@alice @bob : PSK "j1JbIi9WY07rxwcNQ6nbyThKCf9DGxWOyokXIQcAQUnafsNTUJxfsxwk9WYK8fHj"'';
+        mode = "600";
+      };
+  };
+
+  # Common network setup
+  baseNetwork = {
+    # shared hosts file
+    extraHosts = lib.mkVMOverride ''
+      fd::a alice
+      fd::b bob
+      fd::e eve
+    '';
+    # remove all automatic addresses
+    useDHCP = false;
+    interfaces.eth1.ipv4.addresses = lib.mkVMOverride [];
+    interfaces.eth2.ipv4.addresses = lib.mkVMOverride [];
+    # open a port for testing
+    firewall.allowedUDPPorts = [ 1234 ];
+  };
+
+  # Adds an address and route from a to b via Eve
+  addRoute = a: b: {
+    interfaces.eth1.ipv6.addresses =
+      [ { address = a; prefixLength = 64; } ];
+    interfaces.eth1.ipv6.routes =
+      [ { address = b; prefixLength = 128; via = "fd::e"; } ];
+  };
+
+in
+
+{
+  name = "libreswan";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  # Our protagonist
+  nodes.alice = { ... }: {
+    virtualisation.vlans = [ 1 ];
+    networking = baseNetwork // addRoute "fd::a" "fd::b";
+  } // tunnelConfig;
+
+  # Her best friend
+  nodes.bob = { ... }: {
+    virtualisation.vlans = [ 2 ];
+    networking = baseNetwork // addRoute "fd::b" "fd::a";
+  } // tunnelConfig;
+
+  # The malicious network operator
+  nodes.eve = { ... }: {
+    virtualisation.vlans = [ 1 2 ];
+    networking = lib.mkMerge
+      [ baseNetwork
+        { interfaces.br0.ipv6.addresses =
+            [ { address = "fd::e"; prefixLength = 64; } ];
+          bridges.br0.interfaces = [ "eth1" "eth2" ];
+        }
+      ];
+    environment.systemPackages = [ pkgs.tcpdump ];
+    boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+  };
+
+  testScript =
+    ''
+      def alice_to_bob(msg: str):
+          """
+          Sends a message as Alice to Bob
+          """
+          bob.execute("nc -lu ::0 1234 >/tmp/msg &")
+          alice.sleep(1)
+          alice.succeed(f"echo '{msg}' | nc -uw 0 bob 1234")
+          bob.succeed(f"grep '{msg}' /tmp/msg")
+
+
+      def eavesdrop():
+          """
+          Starts eavesdropping on Alice and Bob
+          """
+          match = "src host alice and dst host bob"
+          eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log &")
+
+
+      start_all()
+
+      with subtest("Network is up"):
+          alice.wait_until_succeeds("ping -c1 bob")
+          alice.succeed("systemctl restart ipsec")
+          bob.succeed("systemctl restart ipsec")
+
+      with subtest("Eve can eavesdrop cleartext traffic"):
+          eavesdrop()
+          alice_to_bob("I secretly love turnip")
+          eve.sleep(1)
+          eve.succeed("grep turnip /tmp/log")
+
+      with subtest("Libreswan is ready"):
+          alice.wait_for_unit("ipsec")
+          bob.wait_for_unit("ipsec")
+          alice.succeed("ipsec verify 1>&2")
+
+      with subtest("Alice and Bob can start the tunnel"):
+          alice.execute("ipsec auto --start tunnel >&2 &")
+          bob.succeed("ipsec auto --start tunnel")
+          # apparently this is needed to "wake" the tunnel
+          bob.execute("ping -c1 alice")
+
+      with subtest("Eve no longer can eavesdrop"):
+          eavesdrop()
+          alice_to_bob("Just kidding, I actually like rhubarb")
+          eve.sleep(1)
+          eve.fail("grep rhubarb /tmp/log")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/libuiohook.nix b/nixpkgs/nixos/tests/libuiohook.nix
new file mode 100644
index 000000000000..66c5033d9688
--- /dev/null
+++ b/nixpkgs/nixos/tests/libuiohook.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "libuiohook";
+  meta = with lib.maintainers; { maintainers = [ anoa ]; };
+
+  nodes.client = { nodes, ... }:
+      let user = nodes.client.config.users.users.alice;
+      in {
+        imports = [ ./common/user-account.nix ./common/x11.nix ];
+
+        environment.systemPackages = [ pkgs.libuiohook.test ];
+
+        test-support.displayManager.auto.user = user.name;
+      };
+
+  testScript = { nodes, ... }:
+    let user = nodes.client.config.users.users.alice;
+    in ''
+      client.wait_for_x()
+      client.succeed("su - alice -c ${pkgs.libuiohook.test}/share/uiohook_tests >&2 &")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/libvirtd.nix b/nixpkgs/nixos/tests/libvirtd.nix
new file mode 100644
index 000000000000..df80dcc21a2e
--- /dev/null
+++ b/nixpkgs/nixos/tests/libvirtd.nix
@@ -0,0 +1,68 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "libvirtd";
+  meta.maintainers = with pkgs.lib.maintainers; [ fpletz ];
+
+  nodes = {
+    virthost =
+      { pkgs, ... }:
+      {
+        virtualisation = {
+          cores = 2;
+          memorySize = 2048;
+
+          libvirtd.enable = true;
+          libvirtd.hooks.qemu.is_working = "${pkgs.writeShellScript "testHook.sh" ''
+            touch /tmp/qemu_hook_is_working
+          ''}";
+          libvirtd.nss.enable = true;
+        };
+        boot.supportedFilesystems = [ "zfs" ];
+        networking.hostId = "deadbeef"; # needed for zfs
+        security.polkit.enable = true;
+        environment.systemPackages = with pkgs; [ virt-manager ];
+      };
+  };
+
+  testScript = let
+    nixosInstallISO = (import ../release.nix {}).iso_minimal.${pkgs.stdenv.hostPlatform.system};
+    virshShutdownCmd = if pkgs.stdenv.isx86_64 then "shutdown" else "destroy";
+  in ''
+    start_all()
+
+    virthost.wait_for_unit("multi-user.target")
+
+    with subtest("enable default network"):
+      virthost.succeed("virsh net-start default")
+      virthost.succeed("virsh net-autostart default")
+      virthost.succeed("virsh net-info default")
+
+    with subtest("check if partition disk pools works with parted"):
+      virthost.succeed("fallocate -l100m /tmp/foo; losetup /dev/loop0 /tmp/foo; echo 'label: dos' | sfdisk /dev/loop0")
+      virthost.succeed("virsh pool-create-as foo disk --source-dev /dev/loop0 --target /dev")
+      virthost.succeed("virsh vol-create-as foo loop0p1 25MB")
+      virthost.succeed("virsh vol-create-as foo loop0p2 50MB")
+
+    with subtest("check if virsh zfs pools work"):
+      virthost.succeed("fallocate -l100m /tmp/zfs; losetup /dev/loop1 /tmp/zfs;")
+      virthost.succeed("zpool create zfs_loop /dev/loop1")
+      virthost.succeed("virsh pool-define-as --name zfs_storagepool --source-name zfs_loop --type zfs")
+      virthost.succeed("virsh pool-start zfs_storagepool")
+      virthost.succeed("virsh vol-create-as zfs_storagepool disk1 25MB")
+
+    with subtest("check if nixos install iso boots, network and autostart works"):
+      virthost.succeed(
+        "virt-install -n nixos --osinfo nixos-unstable --memory 1024 --graphics none --disk `find ${nixosInstallISO}/iso -type f | head -n1`,readonly=on --import --noautoconsole --autostart"
+      )
+      virthost.succeed("virsh domstate nixos | grep running")
+      virthost.wait_until_succeeds("ping -c 1 nixos")
+      virthost.succeed("virsh ${virshShutdownCmd} nixos")
+      virthost.wait_until_succeeds("virsh domstate nixos | grep 'shut off'")
+      virthost.shutdown()
+      virthost.wait_for_unit("multi-user.target")
+      virthost.wait_until_succeeds("ping -c 1 nixos")
+
+    with subtest("test if hooks are linked and run"):
+      virthost.succeed("ls /var/lib/libvirt/hooks/qemu.d/is_working")
+      virthost.succeed("ls /tmp/qemu_hook_is_working")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lidarr.nix b/nixpkgs/nixos/tests/lidarr.nix
new file mode 100644
index 000000000000..8230dda53736
--- /dev/null
+++ b/nixpkgs/nixos/tests/lidarr.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "lidarr";
+  meta.maintainers = with lib.maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.lidarr.enable = true; };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("lidarr.service")
+    machine.wait_for_open_port(8686)
+    machine.succeed("curl --fail http://localhost:8686/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lightdm.nix b/nixpkgs/nixos/tests/lightdm.nix
new file mode 100644
index 000000000000..94cebd4a630a
--- /dev/null
+++ b/nixpkgs/nixos/tests/lightdm.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "lightdm";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.lightdm.enable = true;
+    services.xserver.displayManager.defaultSession = "none+icewm";
+    services.xserver.windowManager.icewm.enable = true;
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+  in ''
+    start_all()
+    machine.wait_for_text("${user.description}")
+    machine.screenshot("lightdm")
+    machine.send_chars("${user.password}\n")
+    machine.wait_for_file("${user.home}/.Xauthority")
+    machine.succeed("xauth merge ${user.home}/.Xauthority")
+    machine.wait_for_window("^IceWM ")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lighttpd.nix b/nixpkgs/nixos/tests/lighttpd.nix
new file mode 100644
index 000000000000..daef1584a45c
--- /dev/null
+++ b/nixpkgs/nixos/tests/lighttpd.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "lighttpd";
+  meta.maintainers = with lib.maintainers; [ bjornfor ];
+
+  nodes = {
+    server = {
+      services.lighttpd.enable = true;
+      services.lighttpd.document-root = pkgs.runCommand "document-root" {} ''
+        mkdir -p "$out"
+        echo "hello nixos test" > "$out/file.txt"
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("lighttpd.service")
+    res = server.succeed("curl --fail http://localhost/file.txt")
+    assert "hello nixos test" in res, f"bad server response: '{res}'"
+    server.succeed("systemctl reload lighttpd")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/limesurvey.nix b/nixpkgs/nixos/tests/limesurvey.nix
new file mode 100644
index 000000000000..9a3193991f35
--- /dev/null
+++ b/nixpkgs/nixos/tests/limesurvey.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "limesurvey";
+  meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
+
+  nodes.machine = { ... }: {
+    services.limesurvey = {
+      enable = true;
+      virtualHost = {
+        hostName = "example.local";
+        adminAddr = "root@example.local";
+      };
+    };
+
+    # limesurvey won't work without a dot in the hostname
+    networking.hosts."127.0.0.1" = [ "example.local" ];
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("phpfpm-limesurvey.service")
+    assert "The following surveys are available" in machine.succeed(
+        "curl -f http://example.local/"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/listmonk.nix b/nixpkgs/nixos/tests/listmonk.nix
new file mode 100644
index 000000000000..938c36026a7f
--- /dev/null
+++ b/nixpkgs/nixos/tests/listmonk.nix
@@ -0,0 +1,76 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "listmonk";
+  meta.maintainers = with lib.maintainers; [ raitobezarius ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.mailhog.enable = true;
+    services.listmonk = {
+      enable = true;
+      settings = {
+        admin_username = "listmonk";
+        admin_password = "hunter2";
+      };
+      database = {
+        createLocally = true;
+        # https://github.com/knadh/listmonk/blob/174a48f252a146d7e69dab42724e3329dbe25ebe/internal/messenger/email/email.go#L18-L27
+        settings.smtp = [ {
+          enabled = true;
+          host = "localhost";
+          port = 1025;
+          tls_type = "none";
+        }];
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    start_all()
+
+    basic_auth = "listmonk:hunter2"
+    def generate_listmonk_request(type, url, data=None):
+       if data is None: data = {}
+       json_data = json.dumps(data)
+       return f'curl -u "{basic_auth}" -X {type} "http://localhost:9000/api/{url}" -H "Content-Type: application/json; charset=utf-8" --data-raw \'{json_data}\'''
+
+    machine.wait_for_unit("mailhog.service")
+    machine.wait_for_unit("postgresql.service")
+    machine.wait_for_unit("listmonk.service")
+    machine.wait_for_open_port(1025)
+    machine.wait_for_open_port(8025)
+    machine.wait_for_open_port(9000)
+    machine.succeed("[[ -f /var/lib/listmonk/.db_settings_initialized ]]")
+
+    assert json.loads(machine.succeed(generate_listmonk_request("GET", 'health')))['data'], 'Health endpoint returned unexpected value'
+
+    # A sample subscriber is guaranteed to exist at install-time
+    # A sample transactional template is guaranteed to exist at install-time
+    subscribers = json.loads(machine.succeed(generate_listmonk_request('GET', "subscribers")))['data']['results']
+    templates = json.loads(machine.succeed(generate_listmonk_request('GET', "templates")))['data']
+    tx_template = templates[2]
+
+    # Test transactional endpoint
+    print(machine.succeed(
+      generate_listmonk_request('POST', 'tx', data={'subscriber_id': subscribers[0]['id'], 'template_id': tx_template['id']})
+    ))
+
+    assert 'Welcome Anon Doe' in machine.succeed(
+        "curl --fail http://localhost:8025/api/v2/messages"
+    ), "Failed to find Welcome John Doe inside the messages API endpoint"
+
+    # Test campaign endpoint
+    # Based on https://github.com/knadh/listmonk/blob/174a48f252a146d7e69dab42724e3329dbe25ebe/cmd/campaigns.go#L549 as docs do not exist.
+    campaign_data = json.loads(machine.succeed(
+      generate_listmonk_request('POST', 'campaigns/1/test', data={'template_id': templates[0]['id'], 'subscribers': ['john@example.com'], 'name': 'Test', 'subject': 'NixOS is great', 'lists': [1], 'messenger': 'email'})
+    ))
+
+    assert campaign_data['data']  # This is a boolean asserting if the test was successful or not: https://github.com/knadh/listmonk/blob/174a48f252a146d7e69dab42724e3329dbe25ebe/cmd/campaigns.go#L626
+
+    messages = json.loads(machine.succeed(
+        "curl --fail http://localhost:8025/api/v2/messages"
+    ))
+
+    assert messages['total'] == 2
+  '';
+})
diff --git a/nixpkgs/nixos/tests/litestream.nix b/nixpkgs/nixos/tests/litestream.nix
new file mode 100644
index 000000000000..a281d8538694
--- /dev/null
+++ b/nixpkgs/nixos/tests/litestream.nix
@@ -0,0 +1,101 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "litestream";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ jwygoda ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.litestream = {
+        enable = true;
+        settings = {
+          dbs = [
+            {
+              path = "/var/lib/grafana/data/grafana.db";
+              replicas = [{
+                url = "sftp://foo:bar@127.0.0.1:22/home/foo/grafana";
+              }];
+            }
+          ];
+        };
+      };
+      systemd.services.grafana.serviceConfig.ExecStartPost = "+" + pkgs.writeShellScript "grant-grafana-permissions" ''
+        timeout=10
+
+        while [ ! -f /var/lib/grafana/data/grafana.db ];
+        do
+          if [ "$timeout" == 0 ]; then
+            echo "ERROR: Timeout while waiting for /var/lib/grafana/data/grafana.db."
+            exit 1
+          fi
+
+          sleep 1
+
+          ((timeout--))
+        done
+
+        find /var/lib/grafana -type d -exec chmod -v 775 {} \;
+        find /var/lib/grafana -type f -exec chmod -v 660 {} \;
+      '';
+      services.openssh = {
+        enable = true;
+        allowSFTP = true;
+        listenAddresses = [ { addr = "127.0.0.1"; port = 22; } ];
+      };
+      services.grafana = {
+        enable = true;
+        settings = {
+          security = {
+            admin_user = "admin";
+            admin_password = "admin";
+          };
+
+          server = {
+            http_addr = "localhost";
+            http_port = 3000;
+          };
+
+          database = {
+            type = "sqlite3";
+            path = "/var/lib/grafana/data/grafana.db";
+            wal = true;
+          };
+        };
+      };
+      users.users.foo = {
+        isNormalUser = true;
+        password = "bar";
+      };
+      users.users.litestream.extraGroups = [ "grafana" ];
+    };
+
+  testScript = ''
+    start_all()
+    machine.wait_until_succeeds("test -d /home/foo/grafana")
+    machine.wait_for_open_port(3000)
+    machine.succeed("""
+        curl -sSfN -X PUT -H "Content-Type: application/json" -d '{
+          "oldPassword": "admin",
+          "newPassword": "newpass",
+          "confirmNew": "newpass"
+        }' http://admin:admin@127.0.0.1:3000/api/user/password
+    """)
+    # https://litestream.io/guides/systemd/#simulating-a-disaster
+    machine.systemctl("stop litestream.service")
+    machine.succeed(
+        "rm -f /var/lib/grafana/data/grafana.db "
+        "/var/lib/grafana/data/grafana.db-shm "
+        "/var/lib/grafana/data/grafana.db-wal"
+    )
+    machine.succeed(
+        "litestream restore /var/lib/grafana/data/grafana.db "
+        "&& chown grafana:grafana /var/lib/grafana/data/grafana.db "
+        "&& chmod 660 /var/lib/grafana/data/grafana.db"
+    )
+    machine.systemctl("restart grafana.service")
+    machine.wait_for_open_port(3000)
+    machine.succeed(
+        "curl -sSfN -u admin:newpass http://127.0.0.1:3000/api/org/users | grep admin\@localhost"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/livebook-service.nix b/nixpkgs/nixos/tests/livebook-service.nix
new file mode 100644
index 000000000000..f428412e1644
--- /dev/null
+++ b/nixpkgs/nixos/tests/livebook-service.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "livebook-service";
+
+  nodes = {
+    machine = { config, pkgs, ... }: {
+      imports = [
+        ./common/user-account.nix
+      ];
+
+      services.livebook = {
+        enableUserService = true;
+        environment = {
+          LIVEBOOK_PORT = 20123;
+          LIVEBOOK_COOKIE = "chocolate chip";
+          LIVEBOOK_TOKEN_ENABLED = true;
+
+        };
+        environmentFile = pkgs.writeText "livebook.env" ''
+          LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+        '';
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      sudo = lib.concatStringsSep " " [
+        "XDG_RUNTIME_DIR=/run/user/${toString user.uid}"
+        "sudo"
+        "--preserve-env=XDG_RUNTIME_DIR"
+        "-u"
+        "alice"
+      ];
+    in
+    ''
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("loginctl enable-linger alice")
+      machine.wait_until_succeeds("${sudo} systemctl --user is-active livebook.service")
+      machine.wait_for_open_port(20123)
+
+      machine.succeed("curl -L localhost:20123 | grep 'Type password'")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/lldap.nix b/nixpkgs/nixos/tests/lldap.nix
new file mode 100644
index 000000000000..d6c3a865aa04
--- /dev/null
+++ b/nixpkgs/nixos/tests/lldap.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "lldap";
+
+  nodes.machine = { pkgs, ... }: {
+    services.lldap = {
+      enable = true;
+      settings = {
+        verbose = true;
+        ldap_base_dn = "dc=example,dc=com";
+      };
+    };
+    environment.systemPackages = [ pkgs.openldap ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("lldap.service")
+    machine.wait_for_open_port(3890)
+    machine.wait_for_open_port(17170)
+
+    machine.succeed("curl --location --fail http://localhost:17170/")
+
+    print(
+      machine.succeed('ldapsearch -H ldap://localhost:3890 -D uid=admin,ou=people,dc=example,dc=com -b "ou=people,dc=example,dc=com" -w password')
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/locate.nix b/nixpkgs/nixos/tests/locate.nix
new file mode 100644
index 000000000000..e8ba41812a8f
--- /dev/null
+++ b/nixpkgs/nixos/tests/locate.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+  let inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+  in {
+    name = "locate";
+    meta.maintainers = with pkgs.lib.maintainers; [ chkno ];
+
+    nodes = rec {
+      a = {
+        environment.systemPackages = with pkgs; [ sshfs ];
+        virtualisation.fileSystems = {
+          "/ssh" = {
+            device = "alice@b:/";
+            fsType = "fuse.sshfs";
+            options = [
+              "allow_other"
+              "IdentityFile=/privkey"
+              "noauto"
+              "StrictHostKeyChecking=no"
+              "UserKnownHostsFile=/dev/null"
+            ];
+          };
+        };
+        services.locate = {
+          enable = true;
+          interval = "*:*:0/5";
+        };
+      };
+      b = {
+        services.openssh.enable = true;
+        users.users.alice = {
+          isNormalUser = true;
+          openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # Set up sshfs mount
+      a.succeed(
+          "(umask 077; cat ${snakeOilPrivateKey} > /privkey)"
+      )
+      b.succeed("touch /file-on-b-machine")
+      b.wait_for_open_port(22)
+      a.succeed("mkdir /ssh")
+      a.succeed("mount /ssh")
+
+      # Core locatedb functionality
+      a.succeed("touch /file-on-a-machine-1")
+      a.wait_for_file("/var/cache/locatedb")
+      a.wait_until_succeeds("locate file-on-a-machine-1")
+
+      # Wait for a second update to make sure we're using a locatedb from a run
+      # that began after the sshfs mount
+      a.succeed("touch /file-on-a-machine-2")
+      a.wait_until_succeeds("locate file-on-a-machine-2")
+
+      # We shouldn't be able to see files on the other machine
+      a.fail("locate file-on-b-machine")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/login.nix b/nixpkgs/nixos/tests/login.nix
new file mode 100644
index 000000000000..67f5764a0a16
--- /dev/null
+++ b/nixpkgs/nixos/tests/login.nix
@@ -0,0 +1,68 @@
+import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
+
+{
+  name = "login";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes.machine =
+    { pkgs, lib, ... }:
+    { boot.kernelPackages = lib.mkIf latestKernel pkgs.linuxPackages_latest;
+      sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
+    };
+
+  testScript = ''
+      machine.start(allow_reboot = True)
+
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+      machine.screenshot("postboot")
+
+      with subtest("create user"):
+          machine.succeed("useradd -m alice")
+          machine.succeed("(echo foobar; echo foobar) | passwd alice")
+
+      with subtest("Check whether switching VTs works"):
+          machine.fail("pgrep -f 'agetty.*tty2'")
+          machine.send_key("alt-f2")
+          machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+          machine.wait_for_unit("getty@tty2.service")
+          machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+
+      with subtest("Log in as alice on a virtual console"):
+          machine.wait_until_tty_matches("2", "login: ")
+          machine.send_chars("alice\n")
+          machine.wait_until_tty_matches("2", "login: alice")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("2", "Password: ")
+          machine.send_chars("foobar\n")
+          machine.wait_until_succeeds("pgrep -u alice bash")
+          machine.send_chars("touch done\n")
+          machine.wait_for_file("/home/alice/done")
+
+      with subtest("Systemd gives and removes device ownership as needed"):
+          machine.succeed("getfacl /dev/snd/timer | grep -q alice")
+          machine.send_key("alt-f1")
+          machine.wait_until_succeeds("[ $(fgconsole) = 1 ]")
+          machine.fail("getfacl /dev/snd/timer | grep -q alice")
+          machine.succeed("chvt 2")
+          machine.wait_until_succeeds("getfacl /dev/snd/timer | grep -q alice")
+
+      with subtest("Virtual console logout"):
+          machine.send_chars("exit\n")
+          machine.wait_until_fails("pgrep -u alice bash")
+          machine.screenshot("getty")
+
+      with subtest("Check whether ctrl-alt-delete works"):
+          boot_id1 = machine.succeed("cat /proc/sys/kernel/random/boot_id").strip()
+          assert boot_id1 != ""
+
+          machine.reboot()
+
+          boot_id2 = machine.succeed("cat /proc/sys/kernel/random/boot_id").strip()
+          assert boot_id2 != ""
+
+          assert boot_id1 != boot_id2
+  '';
+})
diff --git a/nixpkgs/nixos/tests/logrotate.nix b/nixpkgs/nixos/tests/logrotate.nix
new file mode 100644
index 000000000000..bcbe89c259ae
--- /dev/null
+++ b/nixpkgs/nixos/tests/logrotate.nix
@@ -0,0 +1,123 @@
+# Test logrotate service works and is enabled by default
+
+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 ];
+  };
+
+  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 invocation
+        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;
+        };
+      };
+    };
+  };
+
+  testScript =
+    ''
+      with subtest("whether logrotate works"):
+          # 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
+          defaultMachine.wait_for_console_text('logrotate.service: Deactivated successfully')
+
+          defaultMachine.succeed(
+              # wtmp is present in default config.
+              "rm -f /var/log/wtmp*",
+              # we need to give it at least 1MB
+              "dd if=/dev/zero of=/var/log/wtmp bs=2M count=1",
+
+              # move into the future and check rotation.
+              "date -s 'now + 1 month + 1 day'")
+          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",
+          )
+          # 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/nixpkgs/nixos/tests/loki.nix b/nixpkgs/nixos/tests/loki.nix
new file mode 100644
index 000000000000..470f80e9db63
--- /dev/null
+++ b/nixpkgs/nixos/tests/loki.nix
@@ -0,0 +1,56 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+{
+  name = "loki";
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  nodes.machine = { ... }: {
+    services.loki = {
+      enable = true;
+      configFile = "${pkgs.grafana-loki.src}/cmd/loki/loki-local-config.yaml";
+    };
+    services.promtail = {
+      enable = true;
+      configuration = {
+        server = {
+          http_listen_port = 9080;
+          grpc_listen_port = 0;
+        };
+        clients = [ { url = "http://localhost:3100/loki/api/v1/push"; } ];
+        scrape_configs = [
+          {
+            job_name = "system";
+            static_configs = [
+              {
+                targets = [ "localhost" ];
+                labels = {
+                  job = "varlogs";
+                  __path__ = "/var/log/*log";
+                };
+              }
+            ];
+          }
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.start
+    machine.wait_for_unit("loki.service")
+    machine.wait_for_unit("promtail.service")
+    machine.wait_for_open_port(3100)
+    machine.wait_for_open_port(9080)
+    machine.succeed("echo 'Loki Ingestion Test' > /var/log/testlog")
+    # should not have access to journal unless specified
+    machine.fail(
+        "systemctl show --property=SupplementaryGroups promtail | grep -q systemd-journal"
+    )
+    machine.wait_until_succeeds(
+        "${pkgs.grafana-loki}/bin/logcli --addr='http://localhost:3100' query --no-labels '{job=\"varlogs\",filename=\"/var/log/testlog\"}' | grep -q 'Loki Ingestion Test'"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lorri/builder.sh b/nixpkgs/nixos/tests/lorri/builder.sh
new file mode 100644
index 000000000000..b586b2bf7985
--- /dev/null
+++ b/nixpkgs/nixos/tests/lorri/builder.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+printf "%s" "${name:?}" > "${out:?}"
diff --git a/nixpkgs/nixos/tests/lorri/default.nix b/nixpkgs/nixos/tests/lorri/default.nix
new file mode 100644
index 000000000000..a4bdc92490ce
--- /dev/null
+++ b/nixpkgs/nixos/tests/lorri/default.nix
@@ -0,0 +1,28 @@
+import ../make-test-python.nix {
+  name = "lorri";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ../../modules/profiles/minimal.nix ];
+    environment.systemPackages = [ pkgs.lorri ];
+  };
+
+  testScript = ''
+    # Copy files over
+    machine.succeed(
+        "cp '${./fake-shell.nix}' shell.nix"
+    )
+    machine.succeed(
+        "cp '${./builder.sh}' builder.sh"
+    )
+
+    # Start the daemon and wait until it is ready
+    machine.execute("lorri daemon > lorri.stdout 2> lorri.stderr &")
+    machine.wait_until_succeeds("grep --fixed-strings 'ready' lorri.stdout")
+
+    # Ping the daemon
+    machine.succeed("lorri internal ping shell.nix")
+
+    # Wait for the daemon to finish the build
+    machine.wait_until_succeeds("grep --fixed-strings 'Completed' lorri.stdout")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/lorri/fake-shell.nix b/nixpkgs/nixos/tests/lorri/fake-shell.nix
new file mode 100644
index 000000000000..9de9d247e542
--- /dev/null
+++ b/nixpkgs/nixos/tests/lorri/fake-shell.nix
@@ -0,0 +1,5 @@
+derivation {
+  system = builtins.currentSystem;
+  name = "fake-shell";
+  builder = ./builder.sh;
+}
diff --git a/nixpkgs/nixos/tests/luks.nix b/nixpkgs/nixos/tests/luks.nix
new file mode 100644
index 000000000000..da1d0c63b95d
--- /dev/null
+++ b/nixpkgs/nixos/tests/luks.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "luks";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/auto-format-root-device.nix ];
+
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 512 ];
+      useBootLoader = true;
+      useEFIBoot = true;
+      # To boot off the encrypted disk, we need to have a init script which comes from the Nix store
+      mountHostNixStore = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.kernelParams = lib.mkOverride 5 [ "console=tty1" ];
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+
+    specialisation = rec {
+      boot-luks.configuration = {
+        boot.initrd.luks.devices = lib.mkVMOverride {
+          # We have two disks and only type one password - key reuse is in place
+          cryptroot.device = "/dev/vdb";
+          cryptroot2.device = "/dev/vdc";
+        };
+        virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      };
+      boot-luks-custom-keymap.configuration = lib.mkMerge [
+        boot-luks.configuration
+        {
+          console.keyMap = "neo";
+        }
+      ];
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_text("Passphrase for")
+    machine.send_chars("supersecret\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+
+    # Boot from the encrypted disk with custom keymap
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-custom-keymap.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_text("Passphrase for")
+    machine.send_chars("havfkhfrkfl\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lvm2/default.nix b/nixpkgs/nixos/tests/lvm2/default.nix
new file mode 100644
index 000000000000..e0358ec2806f
--- /dev/null
+++ b/nixpkgs/nixos/tests/lvm2/default.nix
@@ -0,0 +1,45 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+, lib ? pkgs.lib
+, kernelVersionsToTest ? [ "4.19" "5.4" "5.10" "5.15" "6.1" "latest" ]
+}:
+
+# For quickly running a test, the nixosTests.lvm2.lvm-thinpool-linux-latest attribute is recommended
+let
+  tests = let callTest = p: lib.flip (import p) { inherit system pkgs; }; in {
+    thinpool = { test = callTest ./thinpool.nix; kernelFilter = lib.id; };
+    # we would like to test all versions, but the kernel module currently does not compile against the other versions
+    vdo = { test = callTest ./vdo.nix; kernelFilter = lib.filter (v: v == "5.15"); };
+
+
+    # systemd in stage 1
+    raid-sd-stage-1 = {
+      test = callTest ./systemd-stage-1.nix;
+      kernelFilter = lib.id;
+      flavour = "raid";
+    };
+    thinpool-sd-stage-1 = {
+      test = callTest ./systemd-stage-1.nix;
+      kernelFilter = lib.id;
+      flavour = "thinpool";
+    };
+    vdo-sd-stage-1 = {
+      test = callTest ./systemd-stage-1.nix;
+      kernelFilter = lib.filter (v: v == "5.15");
+      flavour = "vdo";
+    };
+  };
+in
+lib.listToAttrs (
+  lib.filter (x: x.value != {}) (
+    lib.flip lib.concatMap kernelVersionsToTest (version:
+      let
+        v' = lib.replaceStrings [ "." ] [ "_" ] version;
+      in
+      lib.flip lib.mapAttrsToList tests (name: t:
+        lib.nameValuePair "lvm-${name}-linux-${v'}" (lib.optionalAttrs (builtins.elem version (t.kernelFilter kernelVersionsToTest)) (t.test ({ kernelPackages = pkgs."linuxPackages_${v'}"; } // builtins.removeAttrs t [ "test" "kernelFilter" ])))
+      )
+    )
+  )
+)
diff --git a/nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix b/nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix
new file mode 100644
index 000000000000..1c95aadfcb3f
--- /dev/null
+++ b/nixpkgs/nixos/tests/lvm2/systemd-stage-1.nix
@@ -0,0 +1,106 @@
+{ kernelPackages ? null, flavour }: let
+  preparationCode = {
+    raid = ''
+      machine.succeed("vgcreate test_vg /dev/vdb /dev/vdc")
+      machine.succeed("lvcreate -L 512M --type raid0 test_vg -n test_lv")
+    '';
+
+    thinpool = ''
+      machine.succeed("vgcreate test_vg /dev/vdb")
+      machine.succeed("lvcreate -L 512M -T test_vg/test_thin_pool")
+      machine.succeed("lvcreate -n test_lv -V 16G --thinpool test_thin_pool test_vg")
+    '';
+
+    vdo = ''
+      machine.succeed("vgcreate test_vg /dev/vdb")
+      machine.succeed("lvcreate --type vdo -n test_lv -L 6G -V 12G test_vg/vdo_pool_lv")
+    '';
+  }.${flavour};
+
+  extraConfig = {
+    raid = {
+      boot.initrd.kernelModules = [
+        "dm-raid"
+        "raid0"
+      ];
+    };
+
+    thinpool = {
+      services.lvm = {
+        boot.thin.enable = true;
+        dmeventd.enable = true;
+      };
+    };
+
+    vdo = {
+      services.lvm = {
+        boot.vdo.enable = true;
+        dmeventd.enable = true;
+      };
+    };
+  }.${flavour};
+
+  extraCheck = {
+    raid = ''
+      "test_lv" in machine.succeed("lvs --select segtype=raid0")
+    '';
+
+    thinpool = ''
+      "test_lv" in machine.succeed("lvs --select segtype=thin-pool")
+    '';
+
+    vdo = ''
+      "test_lv" in machine.succeed("lvs --select segtype=vdo")
+    '';
+  }.${flavour};
+
+in import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lvm2-${flavour}-systemd-stage-1";
+  meta.maintainers = lib.teams.helsinki-systems.members;
+
+  nodes.machine = { pkgs, lib, ... }: {
+    imports = [ extraConfig ];
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 8192 8192 ];
+      useBootLoader = true;
+      useEFIBoot = true;
+      # To boot off the LVM disk, we need to have a init script which comes from the Nix store.
+      mountHostNixStore = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+    boot.loader.efi.canTouchEfiVariables = true;
+
+    environment.systemPackages = with pkgs; [ e2fsprogs ]; # for mkfs.ext4
+    boot = {
+      initrd.systemd = {
+        enable = true;
+        emergencyAccess = true;
+      };
+      initrd.services.lvm.enable = true;
+      kernelPackages = lib.mkIf (kernelPackages != null) kernelPackages;
+    };
+
+    specialisation.boot-lvm.configuration.virtualisation.rootDevice = "/dev/test_vg/test_lv";
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    # Create a VG for the root
+    ${preparationCode}
+    machine.succeed("mkfs.ext4 /dev/test_vg/test_lv")
+    machine.succeed("mkdir -p /mnt && mount /dev/test_vg/test_lv /mnt && echo hello > /mnt/test && umount /mnt")
+
+    # Boot from LVM
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-lvm.conf")
+    machine.succeed("sync")
+    machine.crash()
+    machine.wait_for_unit("multi-user.target")
+
+    # Ensure we have successfully booted from LVM
+    assert "(initrd)" in machine.succeed("systemd-analyze")  # booted with systemd in stage 1
+    assert "/dev/mapper/test_vg-test_lv on / type ext4" in machine.succeed("mount")
+    assert "hello" in machine.succeed("cat /test")
+    ${extraCheck}
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lvm2/thinpool.nix b/nixpkgs/nixos/tests/lvm2/thinpool.nix
new file mode 100644
index 000000000000..f49c8980613c
--- /dev/null
+++ b/nixpkgs/nixos/tests/lvm2/thinpool.nix
@@ -0,0 +1,34 @@
+{ kernelPackages ? null }:
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lvm2-thinpool";
+  meta.maintainers = lib.teams.helsinki-systems.members;
+
+  nodes.machine = { pkgs, lib, ... }: {
+    virtualisation.emptyDiskImages = [ 4096 ];
+    services.lvm = {
+      boot.thin.enable = true;
+      dmeventd.enable = true;
+    };
+    environment.systemPackages = with pkgs; [ xfsprogs ];
+    environment.etc."lvm/lvm.conf".text = ''
+      activation/thin_pool_autoextend_percent = 10
+      activation/thin_pool_autoextend_threshold = 80
+    '';
+    boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+  };
+
+  testScript = let
+    mkXfsFlags = lib.optionalString (lib.versionOlder kernelPackages.kernel.version "5.10") "-m bigtime=0 -m inobtcount=0";
+  in ''
+    machine.succeed("vgcreate test_vg /dev/vdb")
+    machine.succeed("lvcreate -L 512M -T test_vg/test_thin_pool")
+    machine.succeed("lvcreate -n test_lv -V 16G --thinpool test_thin_pool test_vg")
+    machine.succeed("mkfs.xfs ${mkXfsFlags} /dev/test_vg/test_lv")
+    machine.succeed("mkdir /mnt; mount /dev/test_vg/test_lv /mnt")
+    assert "/dev/mapper/test_vg-test_lv" == machine.succeed("findmnt -no SOURCE /mnt").strip()
+    machine.succeed("dd if=/dev/zero of=/mnt/empty.file bs=1M count=1024")
+    machine.succeed("journalctl -u dm-event.service | grep \"successfully resized\"")
+    machine.succeed("umount /mnt")
+    machine.succeed("vgchange -a n")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lvm2/vdo.nix b/nixpkgs/nixos/tests/lvm2/vdo.nix
new file mode 100644
index 000000000000..75c1fc094e97
--- /dev/null
+++ b/nixpkgs/nixos/tests/lvm2/vdo.nix
@@ -0,0 +1,27 @@
+{ kernelPackages ? null }:
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lvm2-vdo";
+  meta.maintainers = lib.teams.helsinki-systems.members;
+
+  nodes.machine = { pkgs, lib, ... }: {
+    # Minimum required size for VDO volume: 5063921664 bytes
+    virtualisation.emptyDiskImages = [ 8192 ];
+    services.lvm = {
+      boot.vdo.enable = true;
+      dmeventd.enable = true;
+    };
+    environment.systemPackages = with pkgs; [ xfsprogs ];
+    boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+  };
+
+  testScript = ''
+    machine.succeed("vgcreate test_vg /dev/vdb")
+    machine.succeed("lvcreate --type vdo -n vdo_lv -L 6G -V 12G test_vg/vdo_pool_lv")
+    machine.succeed("mkfs.xfs -K /dev/test_vg/vdo_lv")
+    machine.succeed("mkdir /mnt; mount /dev/test_vg/vdo_lv /mnt")
+    assert "/dev/mapper/test_vg-vdo_lv" == machine.succeed("findmnt -no SOURCE /mnt").strip()
+    machine.succeed("umount /mnt")
+    machine.succeed("vdostats")
+    machine.succeed("vgchange -a n")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lxd-image-server.nix b/nixpkgs/nixos/tests/lxd-image-server.nix
new file mode 100644
index 000000000000..619542bdd945
--- /dev/null
+++ b/nixpkgs/nixos/tests/lxd-image-server.nix
@@ -0,0 +1,94 @@
+import ./make-test-python.nix ({ pkgs, lib, ... } :
+
+let
+  lxd-image = import ../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+    };
+  };
+
+  lxd-image-metadata = lxd-image.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-rootfs = lxd-image.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+
+in {
+  name = "lxd-image-server";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mkg20001 patryk27 ];
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      cores = 2;
+
+      memorySize = 2048;
+      diskSize = 4096;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+    };
+
+    security.pki.certificates = [
+      (builtins.readFile ./common/acme/server/ca.cert.pem)
+    ];
+
+    services.nginx = {
+      enable = true;
+    };
+
+    services.lxd-image-server = {
+      enable = true;
+      nginx = {
+        enable = true;
+        domain = "acme.test";
+      };
+    };
+
+    services.nginx.virtualHosts."acme.test" = {
+      enableACME = false;
+      sslCertificate = ./common/acme/server/acme.test.cert.pem;
+      sslCertificateKey = ./common/acme/server/acme.test.key.pem;
+    };
+
+    networking.hosts = {
+      "::1" = [ "acme.test" ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+    machine.wait_for_file("/var/lib/lxd/unix.socket")
+
+    # Wait for lxd to settle
+    machine.succeed("lxd waitready")
+
+    # lxd expects the pool's directory to already exist
+    machine.succeed("mkdir /var/lxd-pool")
+
+    machine.succeed(
+        "lxd init --minimal"
+    )
+
+    machine.succeed(
+        "lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-rootfs}/*/*.tar.xz --alias nixos"
+    )
+
+    loc = "/var/www/simplestreams/images/iats/nixos/amd64/default/v1"
+
+    with subtest("push image to server"):
+        machine.succeed("lxc launch nixos test")
+        machine.sleep(5)
+        machine.succeed("lxc stop -f test")
+        machine.succeed("lxc publish --public test --alias=testimg")
+        machine.succeed("lxc image export testimg")
+        machine.succeed("ls >&2")
+        machine.succeed("mkdir -p " + loc)
+        machine.succeed("mv *.tar.gz " + loc)
+
+    with subtest("pull image from server"):
+        machine.succeed("lxc remote add img https://acme.test --protocol=simplestreams")
+        machine.succeed("lxc image list img: >&2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lxd/container.nix b/nixpkgs/nixos/tests/lxd/container.nix
new file mode 100644
index 000000000000..ef9c3f4bbee7
--- /dev/null
+++ b/nixpkgs/nixos/tests/lxd/container.nix
@@ -0,0 +1,132 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+let
+  releases = import ../../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+
+      # Our tests require `grep` & friends:
+      environment.systemPackages = with pkgs; [ busybox ];
+    };
+  };
+
+  lxd-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-rootfs-squashfs = releases.lxdContainerImageSquashfs.${pkgs.stdenv.hostPlatform.system};
+
+in {
+  name = "lxd-container";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      diskSize = 6144;
+
+      # Since we're testing `limits.cpu`, we've gotta have a known number of
+      # cores to lean on
+      cores = 2;
+
+      # Ditto, for `limits.memory`
+      memorySize = 512;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+    };
+  };
+
+  testScript = ''
+    def instance_is_up(_) -> bool:
+      status, _ = machine.execute("lxc exec container --disable-stdin --force-interactive /run/current-system/sw/bin/true")
+      return status == 0
+
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+    machine.wait_for_file("/var/lib/lxd/unix.socket")
+
+    # Wait for lxd to settle
+    machine.succeed("lxd waitready")
+
+    # no preseed should mean no service
+    machine.fail("systemctl status lxd-preseed.service")
+
+    machine.succeed("lxd init --minimal")
+
+    machine.succeed(
+        "lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-rootfs}/*/*.tar.xz --alias nixos"
+    )
+
+    with subtest("Container can be managed"):
+        machine.succeed("lxc launch nixos container")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+        machine.succeed("echo true | lxc exec container /run/current-system/sw/bin/bash -")
+        machine.succeed("lxc delete -f container")
+
+    with subtest("Squashfs image is functional"):
+        machine.succeed(
+            "lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-rootfs-squashfs} --alias nixos-squashfs"
+        )
+        machine.succeed("lxc launch nixos-squashfs container")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+        machine.succeed("echo true | lxc exec container /run/current-system/sw/bin/bash -")
+        machine.succeed("lxc delete -f container")
+
+    with subtest("Container is mounted with lxcfs inside"):
+        machine.succeed("lxc launch nixos container")
+        with machine.nested("Waiting for instance to start and be usable"):
+            retry(instance_is_up)
+
+        ## ---------- ##
+        ## limits.cpu ##
+
+        machine.succeed("lxc config set container limits.cpu 1")
+        machine.succeed("lxc restart container")
+        with machine.nested("Waiting for instance to start and be usable"):
+            retry(instance_is_up)
+
+        assert (
+            "1"
+            == machine.succeed("lxc exec container grep -- -c ^processor /proc/cpuinfo").strip()
+        )
+
+        machine.succeed("lxc config set container limits.cpu 2")
+        machine.succeed("lxc restart container")
+        with machine.nested("Waiting for instance to start and be usable"):
+            retry(instance_is_up)
+
+        assert (
+            "2"
+            == machine.succeed("lxc exec container grep -- -c ^processor /proc/cpuinfo").strip()
+        )
+
+        ## ------------- ##
+        ## limits.memory ##
+
+        machine.succeed("lxc config set container limits.memory 64MB")
+        machine.succeed("lxc restart container")
+        with machine.nested("Waiting for instance to start and be usable"):
+            retry(instance_is_up)
+
+        assert (
+            "MemTotal:          62500 kB"
+            == machine.succeed("lxc exec container grep -- MemTotal /proc/meminfo").strip()
+        )
+
+        machine.succeed("lxc config set container limits.memory 128MB")
+        machine.succeed("lxc restart container")
+        with machine.nested("Waiting for instance to start and be usable"):
+            retry(instance_is_up)
+
+        assert (
+            "MemTotal:         125000 kB"
+            == machine.succeed("lxc exec container grep -- MemTotal /proc/meminfo").strip()
+        )
+
+        machine.succeed("lxc delete -f container")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lxd/default.nix b/nixpkgs/nixos/tests/lxd/default.nix
new file mode 100644
index 000000000000..20afdd5e48bb
--- /dev/null
+++ b/nixpkgs/nixos/tests/lxd/default.nix
@@ -0,0 +1,12 @@
+{
+  system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. {inherit system config;},
+  handleTestOn,
+}: {
+  container = import ./container.nix {inherit system pkgs;};
+  nftables = import ./nftables.nix {inherit system pkgs;};
+  preseed = import ./preseed.nix {inherit system pkgs;};
+  ui = import ./ui.nix {inherit system pkgs;};
+  virtual-machine = handleTestOn ["x86_64-linux"] ./virtual-machine.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/lxd/nftables.nix b/nixpkgs/nixos/tests/lxd/nftables.nix
new file mode 100644
index 000000000000..e6ce4089d719
--- /dev/null
+++ b/nixpkgs/nixos/tests/lxd/nftables.nix
@@ -0,0 +1,50 @@
+# This test makes sure that lxd stops implicitly depending on iptables when
+# user enabled nftables.
+#
+# It has been extracted from `lxd.nix` for clarity, and because switching from
+# iptables to nftables requires a full reboot, which is a bit hard inside NixOS
+# tests.
+
+import ../make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "lxd-nftables";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      lxd.enable = true;
+    };
+
+    networking = {
+      firewall.enable = false;
+      nftables.enable = true;
+      nftables.tables."filter".family = "inet";
+      nftables.tables."filter".content = ''
+          chain incoming {
+            type filter hook input priority 0;
+            policy accept;
+          }
+
+          chain forward {
+            type filter hook forward priority 0;
+            policy accept;
+          }
+
+          chain output {
+            type filter hook output priority 0;
+            policy accept;
+          }
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("network.target")
+
+    with subtest("When nftables are enabled, lxd doesn't depend on iptables anymore"):
+        machine.succeed("lsmod | grep nf_tables")
+        machine.fail("lsmod | grep ip_tables")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lxd/preseed.nix b/nixpkgs/nixos/tests/lxd/preseed.nix
new file mode 100644
index 000000000000..fb80dcf3893e
--- /dev/null
+++ b/nixpkgs/nixos/tests/lxd/preseed.nix
@@ -0,0 +1,71 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+{
+  name = "lxd-preseed";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      diskSize = 4096;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+
+      lxd.preseed = {
+        networks = [
+          {
+            name = "nixostestbr0";
+            type = "bridge";
+            config = {
+              "ipv4.address" = "10.0.100.1/24";
+              "ipv4.nat" = "true";
+            };
+          }
+        ];
+        profiles = [
+          {
+            name = "nixostest_default";
+            devices = {
+              eth0 = {
+                name = "eth0";
+                network = "nixostestbr0";
+                type = "nic";
+              };
+              root = {
+                path = "/";
+                pool = "default";
+                size = "35GiB";
+                type = "disk";
+              };
+            };
+          }
+        ];
+        storage_pools = [
+          {
+            name = "nixostest_pool";
+            driver = "dir";
+          }
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    def wait_for_preseed(_) -> bool:
+      _, output = machine.systemctl("is-active lxd-preseed.service")
+      return ("inactive" in output)
+
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+    with machine.nested("Waiting for preseed to complete"):
+      retry(wait_for_preseed)
+
+    with subtest("Verify preseed resources created"):
+      machine.succeed("lxc profile show nixostest_default")
+      machine.succeed("lxc network info nixostestbr0")
+      machine.succeed("lxc storage show nixostest_pool")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lxd/ui.nix b/nixpkgs/nixos/tests/lxd/ui.nix
new file mode 100644
index 000000000000..c442f44ab81c
--- /dev/null
+++ b/nixpkgs/nixos/tests/lxd/ui.nix
@@ -0,0 +1,66 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lxd-ui";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      lxd.enable = true;
+      lxd.ui.enable = true;
+    };
+
+    environment.systemPackages =
+      let
+        seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
+          {
+            libraries = with pkgs.python3Packages; [ selenium ];
+          } ''
+          from selenium import webdriver
+          from selenium.webdriver.common.by import By
+          from selenium.webdriver.firefox.options import Options
+          from selenium.webdriver.support.ui import WebDriverWait
+
+          options = Options()
+          options.add_argument("--headless")
+          service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+
+          driver = webdriver.Firefox(options=options, service=service)
+          driver.implicitly_wait(10)
+          driver.get("https://localhost:8443/ui")
+
+          wait = WebDriverWait(driver, 60)
+
+          assert len(driver.find_elements(By.CLASS_NAME, "l-application")) > 0
+          assert len(driver.find_elements(By.CLASS_NAME, "l-navigation__drawer")) > 0
+
+          driver.close()
+        '';
+      in
+      with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
+  };
+
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+    machine.wait_for_file("/var/lib/lxd/unix.socket")
+
+    # Wait for lxd to settle
+    machine.succeed("lxd waitready")
+
+    # Configure LXC listen address
+    machine.succeed("lxc config set core.https_address :8443")
+    machine.succeed("systemctl restart lxd")
+
+    # Check that the LXD_UI environment variable is populated in the systemd unit
+    machine.succeed("cat /etc/systemd/system/lxd.service | grep 'LXD_UI'")
+
+    # Ensure the endpoint returns an HTML page with 'LXD UI' in the title
+    machine.succeed("curl -kLs https://localhost:8443/ui | grep '<title>LXD UI</title>'")
+
+    # Ensure the application is actually rendered by the Javascript
+    machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/lxd/virtual-machine.nix b/nixpkgs/nixos/tests/lxd/virtual-machine.nix
new file mode 100644
index 000000000000..2a9dd8fcdbf6
--- /dev/null
+++ b/nixpkgs/nixos/tests/lxd/virtual-machine.nix
@@ -0,0 +1,64 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  releases = import ../../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+
+      # Our tests require `grep` & friends:
+      environment.systemPackages = with pkgs; [busybox];
+    };
+  };
+
+  lxd-image-metadata = releases.lxdVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-disk = releases.lxdVirtualMachineImage.${pkgs.stdenv.hostPlatform.system};
+
+  instance-name = "instance1";
+in {
+  name = "lxd-virtual-machine";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = {lib, ...}: {
+    virtualisation = {
+      diskSize = 4096;
+
+      cores = 2;
+
+      # Ensure we have enough memory for the nested virtual machine
+      memorySize = 1024;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+    };
+  };
+
+  testScript = ''
+    def instance_is_up(_) -> bool:
+      status, _ = machine.execute("lxc exec ${instance-name} --disable-stdin --force-interactive /run/current-system/sw/bin/true")
+      return status == 0
+
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+    machine.wait_for_file("/var/lib/lxd/unix.socket")
+
+    # Wait for lxd to settle
+    machine.succeed("lxd waitready")
+
+    machine.succeed("lxd init --minimal")
+
+    with subtest("virtual-machine image can be imported"):
+        machine.succeed("lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-disk}/nixos.qcow2 --alias nixos")
+
+    with subtest("virtual-machine can be launched and become available"):
+        machine.succeed("lxc launch nixos ${instance-name} --vm --config limits.memory=512MB --config security.secureboot=false")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+    with subtest("lxd-agent is started"):
+        machine.succeed("lxc exec ${instance-name} systemctl is-active lxd-agent")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/maddy/default.nix b/nixpkgs/nixos/tests/maddy/default.nix
new file mode 100644
index 000000000000..043906863e64
--- /dev/null
+++ b/nixpkgs/nixos/tests/maddy/default.nix
@@ -0,0 +1,6 @@
+{ handleTest }:
+
+{
+  unencrypted = handleTest ./unencrypted.nix { };
+  tls = handleTest ./tls.nix { };
+}
diff --git a/nixpkgs/nixos/tests/maddy/tls.nix b/nixpkgs/nixos/tests/maddy/tls.nix
new file mode 100644
index 000000000000..44da4cf2a3cf
--- /dev/null
+++ b/nixpkgs/nixos/tests/maddy/tls.nix
@@ -0,0 +1,94 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+let
+  certs = import ../common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in {
+  name = "maddy-tls";
+  meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
+
+  nodes = {
+    server = { options, ... }: {
+      services.maddy = {
+        enable = true;
+        hostname = domain;
+        primaryDomain = domain;
+        openFirewall = true;
+        ensureAccounts = [ "postmaster@${domain}" ];
+        ensureCredentials = {
+          # Do not use this in production. This will make passwords world-readable
+          # in the Nix store
+          "postmaster@${domain}".passwordFile = "${pkgs.writeText "postmaster" "test"}";
+        };
+        tls = {
+          loader = "file";
+          certificates = [{
+            certPath = "${certs.${domain}.cert}";
+            keyPath = "${certs.${domain}.key}";
+          }];
+        };
+        # Enable TLS listeners. Configuring this via the module is not yet
+        # implemented.
+        config = builtins.replaceStrings [
+          "imap tcp://0.0.0.0:143"
+          "submission tcp://0.0.0.0:587"
+        ] [
+          "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
+          "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
+        ] options.services.maddy.config.default;
+      };
+      # Not covered by openFirewall yet
+      networking.firewall.allowedTCPPorts = [ 993 465 ];
+    };
+
+    client = { nodes, ... }: {
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+      networking.extraHosts = ''
+        ${nodes.server.networking.primaryIPAddress} ${domain}
+     '';
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "send-testmail" { } ''
+          import smtplib
+          import ssl
+          from email.mime.text import MIMEText
+
+          context = ssl.create_default_context()
+          msg = MIMEText("Hello World")
+          msg['Subject'] = 'Test'
+          msg['From'] = "postmaster@${domain}"
+          msg['To'] = "postmaster@${domain}"
+          with smtplib.SMTP_SSL(host='${domain}', port=465, context=context) as smtp:
+              smtp.login('postmaster@${domain}', 'test')
+              smtp.sendmail(
+                'postmaster@${domain}', 'postmaster@${domain}', msg.as_string()
+              )
+        '')
+        (pkgs.writers.writePython3Bin "test-imap" { } ''
+          import imaplib
+
+          with imaplib.IMAP4_SSL('${domain}') as imap:
+              imap.login('postmaster@${domain}', 'test')
+              imap.select()
+              status, refs = imap.search(None, 'ALL')
+              assert status == 'OK'
+              assert len(refs) == 1
+              status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+              assert status == 'OK'
+              assert msg[0][1].strip() == b"Hello World"
+        '')
+      ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("maddy.service")
+    server.wait_for_open_port(143)
+    server.wait_for_open_port(993)
+    server.wait_for_open_port(587)
+    server.wait_for_open_port(465)
+    client.succeed("send-testmail")
+    client.succeed("test-imap")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/maddy/unencrypted.nix b/nixpkgs/nixos/tests/maddy/unencrypted.nix
new file mode 100644
index 000000000000..2420d461e4e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/maddy/unencrypted.nix
@@ -0,0 +1,60 @@
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "maddy-unencrypted";
+  meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
+
+  nodes = {
+    server = { ... }: {
+      services.maddy = {
+        enable = true;
+        hostname = "server";
+        primaryDomain = "server";
+        openFirewall = true;
+        ensureAccounts = [ "postmaster@server" ];
+        ensureCredentials = {
+          # Do not use this in production. This will make passwords world-readable
+          # in the Nix store
+          "postmaster@server".passwordFile = "${pkgs.writeText "postmaster" "test"}";
+        };
+      };
+    };
+
+    client = { ... }: {
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "send-testmail" { } ''
+          import smtplib
+          from email.mime.text import MIMEText
+
+          msg = MIMEText("Hello World")
+          msg['Subject'] = 'Test'
+          msg['From'] = "postmaster@server"
+          msg['To'] = "postmaster@server"
+          with smtplib.SMTP('server', 587) as smtp:
+              smtp.login('postmaster@server', 'test')
+              smtp.sendmail('postmaster@server', 'postmaster@server', msg.as_string())
+        '')
+        (pkgs.writers.writePython3Bin "test-imap" { } ''
+          import imaplib
+
+          with imaplib.IMAP4('server') as imap:
+              imap.login('postmaster@server', 'test')
+              imap.select()
+              status, refs = imap.search(None, 'ALL')
+              assert status == 'OK'
+              assert len(refs) == 1
+              status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+              assert status == 'OK'
+              assert msg[0][1].strip() == b"Hello World"
+        '')
+      ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("maddy.service")
+    server.wait_for_open_port(143)
+    server.wait_for_open_port(587)
+    client.succeed("send-testmail")
+    client.succeed("test-imap")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/maestral.nix b/nixpkgs/nixos/tests/maestral.nix
new file mode 100644
index 000000000000..67a265926187
--- /dev/null
+++ b/nixpkgs/nixos/tests/maestral.nix
@@ -0,0 +1,73 @@
+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.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.wait_for_file("/tmp/xauth_*")
+        gui.succeed("xauth merge /tmp/xauth_*")
+        gui.wait_for_window("^Desktop ")
+        gui.wait_for_unit("maestral.service", "${user.name}")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/magic-wormhole-mailbox-server.nix b/nixpkgs/nixos/tests/magic-wormhole-mailbox-server.nix
new file mode 100644
index 000000000000..54088ac60f28
--- /dev/null
+++ b/nixpkgs/nixos/tests/magic-wormhole-mailbox-server.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "magic-wormhole-mailbox-server";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      networking.firewall.allowedTCPPorts = [ 4000 ];
+      services.magic-wormhole-mailbox-server.enable = true;
+    };
+
+    client_alice = { ... }: {
+      networking.firewall.enable = false;
+      environment.systemPackages = [ pkgs.magic-wormhole ];
+    };
+
+    client_bob = { ... }: {
+      environment.systemPackages = [ pkgs.magic-wormhole ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    # Start the wormhole relay server
+    server.wait_for_unit("magic-wormhole-mailbox-server.service")
+    server.wait_for_open_port(4000)
+
+    # Create a secret file and send it to Bob
+    client_alice.succeed("echo mysecret > secretfile")
+    client_alice.succeed("wormhole --relay-url=ws://server:4000/v1 send -0 secretfile >&2 &")
+
+    # Retrieve a secret file from Alice and check its content
+    client_bob.succeed("wormhole --relay-url=ws://server:4000/v1 receive -0 --accept-file")
+    client_bob.succeed("grep mysecret secretfile")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/magnetico.nix b/nixpkgs/nixos/tests/magnetico.nix
new file mode 100644
index 000000000000..ee84aacaf7a7
--- /dev/null
+++ b/nixpkgs/nixos/tests/magnetico.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  port = 8081;
+in
+{
+  name = "magnetico";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    networking.firewall.allowedTCPPorts = [ 9000 ];
+
+    services.magnetico = {
+      enable = true;
+      crawler.port = 9000;
+      web.port = port;
+      web.credentials.user = "$2y$12$P88ZF6soFthiiAeXnz64aOWDsY3Dw7Yw8fZ6GtiqFNjknD70zDmNe";
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("magneticod")
+      machine.wait_for_unit("magneticow")
+      machine.wait_for_open_port(${toString port})
+      machine.succeed(
+          "${pkgs.curl}/bin/curl --fail "
+          + "-u user:password http://localhost:${toString port}"
+      )
+      machine.fail(
+          "${pkgs.curl}/bin/curl --fail "
+          + "-u user:wrongpwd http://localhost:${toString port}"
+      )
+      machine.shutdown()
+    '';
+})
diff --git a/nixpkgs/nixos/tests/mailcatcher.nix b/nixpkgs/nixos/tests/mailcatcher.nix
new file mode 100644
index 000000000000..627ef56617e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/mailcatcher.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "mailcatcher";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.mailcatcher.enable = true;
+
+      programs.msmtp = {
+        enable = true;
+        accounts.default = {
+          host = "localhost";
+          port = 1025;
+        };
+      };
+
+      environment.systemPackages = [ pkgs.mailutils ];
+    };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("mailcatcher.service")
+    machine.wait_for_open_port(1025)
+    machine.succeed(
+        'echo "this is the body of the email" | mail -s "subject" root@example.org'
+    )
+    assert "this is the body of the email" in machine.succeed(
+        "curl -f http://localhost:1080/messages/1.source"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mailhog.nix b/nixpkgs/nixos/tests/mailhog.nix
new file mode 100644
index 000000000000..e3c2da37a3c8
--- /dev/null
+++ b/nixpkgs/nixos/tests/mailhog.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "mailhog";
+  meta.maintainers = with lib.maintainers; [ jojosch ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.mailhog.enable = true;
+
+    environment.systemPackages = with pkgs; [ swaks ];
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("mailhog.service")
+    machine.wait_for_open_port(1025)
+    machine.wait_for_open_port(8025)
+    machine.succeed(
+        'echo "this is the body of the email" | swaks --to root@example.org --body - --server localhost:1025'
+    )
+    assert "this is the body of the email" in machine.succeed(
+        "curl --fail http://localhost:8025/api/v2/messages"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mailman.nix b/nixpkgs/nixos/tests/mailman.nix
new file mode 100644
index 000000000000..f9b43861a12f
--- /dev/null
+++ b/nixpkgs/nixos/tests/mailman.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix {
+  name = "mailman";
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ mailutils ];
+
+    services.mailman.enable = true;
+    services.mailman.serve.enable = true;
+    services.mailman.siteOwner = "postmaster@example.com";
+    services.mailman.webHosts = [ "example.com" ];
+
+    services.postfix.enable = true;
+    services.postfix.destination = [ "example.com" "example.net" ];
+    services.postfix.relayDomains = [ "hash:/var/lib/mailman/data/postfix_domains" ];
+    services.postfix.config.local_recipient_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" "proxy:unix:passwd.byname" ];
+    services.postfix.config.transport_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ];
+
+    users.users.user = { isNormalUser = true; };
+
+    virtualisation.memorySize = 2048;
+
+    specialisation.restApiPassFileSystem.configuration = {
+      services.mailman.restApiPassFile = "/var/lib/mailman/pass";
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    restApiPassFileSystem = "${nodes.machine.system.build.toplevel}/specialisation/restApiPassFileSystem";
+  in ''
+    def check_mail(_) -> bool:
+        status, _ = machine.execute("grep -q hello /var/spool/mail/user/new/*")
+        return status == 0
+
+    def try_api(_) -> bool:
+        status, _ = machine.execute("curl -s http://localhost:8001/")
+        return status == 0
+
+    def wait_for_api():
+        with machine.nested("waiting for Mailman REST API to be available"):
+            retry(try_api)
+
+    machine.wait_for_unit("mailman.service")
+    wait_for_api()
+
+    with subtest("subscription and delivery"):
+        creds = machine.succeed("su -s /bin/sh -c 'mailman info' mailman | grep '^REST credentials: ' | sed 's/^REST credentials: //'").strip()
+        machine.succeed(f"curl --fail-with-body -sLSu {creds} -d mail_host=example.com http://localhost:8001/3.1/domains")
+        machine.succeed(f"curl --fail-with-body -sLSu {creds} -d fqdn_listname=list@example.com http://localhost:8001/3.1/lists")
+        machine.succeed(f"curl --fail-with-body -sLSu {creds} -d list_id=list.example.com -d subscriber=root@example.com -d pre_confirmed=True -d pre_verified=True -d send_welcome_message=False http://localhost:8001/3.1/members")
+        machine.succeed(f"curl --fail-with-body -sLSu {creds} -d list_id=list.example.com -d subscriber=user@example.net -d pre_confirmed=True -d pre_verified=True -d send_welcome_message=False http://localhost:8001/3.1/members")
+        machine.succeed("mail -a 'From: root@example.com' -s hello list@example.com < /dev/null")
+        with machine.nested("waiting for mail from list"):
+            retry(check_mail)
+
+    with subtest("Postorius"):
+        machine.succeed("curl --fail-with-body -sILS http://localhost/")
+
+    with subtest("restApiPassFile"):
+        machine.succeed("echo secretpassword > /var/lib/mailman/pass")
+        machine.succeed("${restApiPassFileSystem}/bin/switch-to-configuration test >&2")
+        machine.succeed("grep secretpassword /etc/mailman.cfg")
+        machine.succeed("su -s /bin/sh -c 'mailman info' mailman | grep secretpassword")
+        wait_for_api()
+        machine.succeed("curl --fail-with-body -sLSu restadmin:secretpassword http://localhost:8001/3.1/domains")
+        machine.succeed("curl --fail-with-body -sILS http://localhost/")
+
+    with subtest("service locking"):
+        machine.fail("su -s /bin/sh -c 'mailman start' mailman")
+        machine.execute("systemctl kill --signal=SIGKILL mailman")
+        machine.succeed("systemctl restart mailman")
+        wait_for_api()
+  '';
+}
diff --git a/nixpkgs/nixos/tests/make-test-python.nix b/nixpkgs/nixos/tests/make-test-python.nix
new file mode 100644
index 000000000000..28569f1d2955
--- /dev/null
+++ b/nixpkgs/nixos/tests/make-test-python.nix
@@ -0,0 +1,9 @@
+f: {
+  system ? builtins.currentSystem,
+  pkgs ? import ../.. { inherit system; config = {}; overlays = []; },
+  ...
+} @ args:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+makeTest (if pkgs.lib.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f)
diff --git a/nixpkgs/nixos/tests/man.nix b/nixpkgs/nixos/tests/man.nix
new file mode 100644
index 000000000000..1ff5af4e8059
--- /dev/null
+++ b/nixpkgs/nixos/tests/man.nix
@@ -0,0 +1,100 @@
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+  manImplementations = [
+    "mandoc"
+    "man-db"
+  ];
+
+  machineNames = builtins.map machineSafe manImplementations;
+
+  makeConfig = useImpl: {
+    # Note: mandoc currently can't index symlinked section directories.
+    # So if a man section comes from one package exclusively (e. g.
+    # 1p from man-pages-posix and 2 from man-pages), it isn't searchable.
+    environment.systemPackages = [
+      pkgs.man-pages
+      pkgs.openssl
+      pkgs.libunwind
+    ];
+
+    documentation = {
+      enable = true;
+      nixos.enable = lib.mkForce true;
+      dev.enable = true;
+      man = {
+        enable = true;
+        generateCaches = true;
+      } // lib.listToAttrs (builtins.map (impl: {
+        name = impl;
+        value = {
+          enable = useImpl == impl;
+        };
+      }) manImplementations);
+    };
+  };
+
+  machineSafe = builtins.replaceStrings [ "-" ] [ "_" ];
+in {
+  name = "man";
+  meta.maintainers = [ lib.maintainers.sternenseemann ];
+
+  nodes = lib.listToAttrs (builtins.map (i: {
+    name = machineSafe i;
+    value = makeConfig i;
+  }) manImplementations);
+
+  testScript = ''
+    import re
+    start_all()
+
+    def match_man_k(page, section, haystack):
+      """
+      Check if the man page {page}({section}) occurs in
+      the output of `man -k` given as haystack. Note:
+      This is not super reliable, e. g. it can't deal
+      with man pages that are in multiple sections.
+      """
+
+      for line in haystack.split("\n"):
+        # man -k can look like this:
+        # page(3) - bla
+        # page (3) - bla
+        # pagea, pageb (3, 3P) - foo
+        # pagea, pageb, pagec(3) - bar
+        pages = line.split("(")[0]
+        sections = re.search("\\([a-zA-Z1-9, ]+\\)", line)
+        if sections is None:
+          continue
+        else:
+          sections = sections.group(0)[1:-1]
+
+        if page in pages and f'{section}' in sections:
+          return True
+
+      return False
+
+  '' + lib.concatMapStrings (machine: ''
+    with subtest("Test direct man page lookups in ${machine}"):
+      # man works
+      ${machine}.succeed("man man > /dev/null")
+      # devman works
+      ${machine}.succeed("man 3 libunwind > /dev/null")
+      # NixOS configuration man page is installed
+      ${machine}.succeed("man configuration.nix > /dev/null")
+
+    with subtest("Test generateCaches via man -k in ${machine}"):
+      expected = [
+        ("openssl", "ssl", 3),
+        ("unwind", "libunwind", 3),
+        ("user", "useradd", 8),
+        ("user", "userdel", 8),
+        ("mem", "free", 3),
+        ("mem", "free", 1),
+      ]
+
+      for (keyword, page, section) in expected:
+        matches = ${machine}.succeed(f"man -k {keyword}")
+        if not match_man_k(page, section, matches):
+          raise Exception(f"{page}({section}) missing in matches: {matches}")
+  '') machineNames;
+})
diff --git a/nixpkgs/nixos/tests/mate.nix b/nixpkgs/nixos/tests/mate.nix
new file mode 100644
index 000000000000..48582e18d520
--- /dev/null
+++ b/nixpkgs/nixos/tests/mate.nix
@@ -0,0 +1,81 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "mate";
+
+  meta = {
+    maintainers = lib.teams.mate.members;
+  };
+
+  nodes.machine = { ... }: {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    services.xserver.enable = true;
+
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+
+    services.xserver.desktopManager.mate.enable = true;
+
+    # Silence log spam due to no sound drivers loaded:
+    # ALSA lib confmisc.c:855:(parse_card) cannot find card '0'
+    hardware.pulseaudio.enable = true;
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      env = "DISPLAY=:0.0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
+    in
+    ''
+      with subtest("Wait for login"):
+          machine.wait_for_x()
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if MATE session components actually start"):
+          machine.wait_until_succeeds("pgrep marco")
+          machine.wait_for_window("marco")
+          machine.wait_until_succeeds("pgrep mate-panel")
+          machine.wait_for_window("Top Panel")
+          machine.wait_for_window("Bottom Panel")
+          machine.wait_until_succeeds("pgrep caja")
+          machine.wait_for_window("Caja")
+          machine.wait_for_text('(Applications|Places|System)')
+          machine.wait_for_text('(Computer|Home|Trash)')
+
+      with subtest("Lock the screen"):
+          machine.wait_until_succeeds("su - ${user.name} -c '${env} mate-screensaver-command -q' | grep 'The screensaver is inactive'")
+          machine.succeed("su - ${user.name} -c '${env} mate-screensaver-command -l >&2 &'")
+          machine.wait_until_succeeds("su - ${user.name} -c '${env} mate-screensaver-command -q' | grep 'The screensaver is active'")
+          machine.sleep(2)
+          machine.send_chars("${user.password}", delay=0.2)
+          machine.wait_for_text("${user.description}")
+          machine.screenshot("screensaver")
+          machine.send_chars("\n")
+          machine.wait_until_succeeds("su - ${user.name} -c '${env} mate-screensaver-command -q' | grep 'The screensaver is inactive'")
+
+      with subtest("Open MATE control center"):
+          machine.succeed("su - ${user.name} -c '${env} mate-control-center >&2 &'")
+          machine.wait_for_window("Control Center")
+          machine.wait_for_text('(Groups|Administration|Hardware)')
+
+      with subtest("Open MATE terminal"):
+          machine.succeed("su - ${user.name} -c '${env} mate-terminal >&2 &'")
+          machine.wait_for_window("Terminal")
+
+      with subtest("Check if MATE has ever coredumped"):
+          machine.fail("coredumpctl --json=short | grep -E 'mate|marco|caja'")
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/matomo.nix b/nixpkgs/nixos/tests/matomo.nix
new file mode 100644
index 000000000000..130f3dd8485a
--- /dev/null
+++ b/nixpkgs/nixos/tests/matomo.nix
@@ -0,0 +1,54 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  matomoTest = package:
+  makeTest {
+    name = "matomo";
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.matomo = {
+        package = package;
+        enable = true;
+        nginx = {
+          forceSSL = false;
+          enableACME = false;
+        };
+      };
+      services.mysql = {
+        enable = true;
+        package = pkgs.mariadb;
+      };
+      services.nginx.enable = true;
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("mysql.service")
+      machine.wait_for_unit("phpfpm-matomo.service")
+      machine.wait_for_unit("nginx.service")
+
+      # without the grep the command does not produce valid utf-8 for some reason
+      with subtest("welcome screen loads"):
+          machine.succeed(
+              "curl -sSfL http://localhost/ | grep '<title>Matomo[^<]*Installation'"
+          )
+    '';
+  };
+in {
+  matomo = matomoTest pkgs.matomo // {
+    name = "matomo";
+    meta.maintainers = with maintainers; [ florianjacob kiwi mmilata twey boozedog ];
+  };
+  matomo-beta = matomoTest pkgs.matomo-beta // {
+    name = "matomo-beta";
+    meta.maintainers = with maintainers; [ florianjacob kiwi mmilata twey boozedog ];
+  };
+  matomo_5 = matomoTest pkgs.matomo_5 // {
+    name = "matomo-5";
+    meta.maintainers = with maintainers; [ florianjacob kiwi mmilata twey boozedog ] ++ lib.teams.flyingcircus.members;
+  };
+}
diff --git a/nixpkgs/nixos/tests/matrix/appservice-irc.nix b/nixpkgs/nixos/tests/matrix/appservice-irc.nix
new file mode 100644
index 000000000000..78c53024ca6c
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/appservice-irc.nix
@@ -0,0 +1,225 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  let
+    homeserverUrl = "http://homeserver:8008";
+  in
+  {
+    name = "matrix-appservice-irc";
+    meta = {
+      maintainers = pkgs.matrix-appservice-irc.meta.maintainers;
+    };
+
+    nodes = {
+      homeserver = { pkgs, ... }: {
+        # We'll switch to this once the config is copied into place
+        specialisation.running.configuration = {
+          services.matrix-synapse = {
+            enable = true;
+            settings = {
+              database.name = "sqlite3";
+              app_service_config_files = [ "/registration.yml" ];
+
+              enable_registration = true;
+
+              # don't use this in production, always use some form of verification
+              enable_registration_without_verification = true;
+
+              listeners = [ {
+                # The default but tls=false
+                bind_addresses = [
+                  "0.0.0.0"
+                ];
+                port = 8008;
+                resources = [ {
+                  "compress" = true;
+                  "names" = [ "client" ];
+                } {
+                  "compress" = false;
+                  "names" = [ "federation" ];
+                } ];
+                tls = false;
+                type = "http";
+              } ];
+            };
+          };
+
+          networking.firewall.allowedTCPPorts = [ 8008 ];
+        };
+      };
+
+      ircd = { pkgs, ... }: {
+        services.ngircd = {
+          enable = true;
+          config = ''
+            [Global]
+              Name = ircd.ircd
+              Info = Server Info Text
+              AdminInfo1 = _
+
+            [Channel]
+              Name = #test
+              Topic = a cool place
+
+            [Options]
+              PAM = no
+          '';
+        };
+        networking.firewall.allowedTCPPorts = [ 6667 ];
+      };
+
+      appservice = { pkgs, ... }: {
+        services.matrix-appservice-irc = {
+          enable = true;
+          registrationUrl = "http://appservice:8009";
+
+          settings = {
+            homeserver.url = homeserverUrl;
+            homeserver.domain = "homeserver";
+
+            ircService.servers."ircd" = {
+              name = "IRCd";
+              port = 6667;
+              dynamicChannels = {
+                enabled = true;
+                aliasTemplate = "#irc_$CHANNEL";
+              };
+            };
+          };
+        };
+
+        networking.firewall.allowedTCPPorts = [ 8009 ];
+      };
+
+      client = { pkgs, ... }: {
+        environment.systemPackages = [
+          (pkgs.writers.writePython3Bin "do_test"
+          {
+            libraries = [ pkgs.python3Packages.matrix-nio ];
+            flakeIgnore = [
+              # We don't live in the dark ages anymore.
+              # Languages like Python that are whitespace heavy will overrun
+              # 79 characters..
+              "E501"
+            ];
+          } ''
+              import sys
+              import socket
+              import functools
+              from time import sleep
+              import asyncio
+
+              from nio import AsyncClient, RoomMessageText, JoinResponse
+
+
+              async def matrix_room_message_text_callback(matrix: AsyncClient, msg: str, _r, e):
+                  print("Received matrix text message: ", e)
+                  if msg in e.body:
+                      print("Received hi from IRC")
+                      await matrix.close()
+                      exit(0)  # Actual exit point
+
+
+              class IRC:
+                  def __init__(self):
+                      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                      sock.connect(("ircd", 6667))
+                      sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+                      sock.send(b"USER bob bob bob :bob\n")
+                      sock.send(b"NICK bob\n")
+                      self.sock = sock
+
+                  def join(self, room: str):
+                      self.sock.send(f"JOIN {room}\n".encode())
+
+                  def privmsg(self, room: str, msg: str):
+                      self.sock.send(f"PRIVMSG {room} :{msg}\n".encode())
+
+                  def expect_msg(self, body: str):
+                      buffer = ""
+                      while True:
+                          buf = self.sock.recv(1024).decode()
+                          buffer += buf
+                          if body in buffer:
+                              return
+
+
+              async def run(homeserver: str):
+                  irc = IRC()
+
+                  matrix = AsyncClient(homeserver)
+                  response = await matrix.register("alice", "foobar")
+                  print("Matrix register response: ", response)
+
+                  response = await matrix.join("#irc_#test:homeserver")
+                  print("Matrix join room response:", response)
+                  assert isinstance(response, JoinResponse)
+                  room_id = response.room_id
+
+                  irc.join("#test")
+                  # FIXME: what are we waiting on here? Matrix? IRC? Both?
+                  # 10s seem bad for busy hydra machines.
+                  sleep(10)
+
+                  # Exchange messages
+                  print("Sending text message to matrix room")
+                  response = await matrix.room_send(
+                      room_id=room_id,
+                      message_type="m.room.message",
+                      content={"msgtype": "m.text", "body": "hi from matrix"},
+                  )
+                  print("Matrix room send response: ", response)
+                  irc.privmsg("#test", "hi from irc")
+
+                  print("Waiting for the matrix message to appear on the IRC side...")
+                  irc.expect_msg("hi from matrix")
+
+                  callback = functools.partial(
+                      matrix_room_message_text_callback, matrix, "hi from irc"
+                  )
+                  matrix.add_event_callback(callback, RoomMessageText)
+
+                  print("Waiting for matrix message...")
+                  await matrix.sync_forever()
+
+                  exit(1)  # Unreachable
+
+
+              if __name__ == "__main__":
+                  asyncio.run(run(sys.argv[1]))
+            ''
+          )
+        ];
+      };
+    };
+
+    testScript = ''
+      import pathlib
+      import os
+
+      start_all()
+
+      ircd.wait_for_unit("ngircd.service")
+      ircd.wait_for_open_port(6667)
+
+      with subtest("start the appservice"):
+          appservice.wait_for_unit("matrix-appservice-irc.service")
+          appservice.wait_for_open_port(8009)
+
+      with subtest("copy the registration file"):
+          appservice.copy_from_vm("/var/lib/matrix-appservice-irc/registration.yml")
+          homeserver.copy_from_host(
+              str(pathlib.Path(os.environ.get("out", os.getcwd())) / "registration.yml"), "/"
+          )
+          homeserver.succeed("chmod 444 /registration.yml")
+
+      with subtest("start the homeserver"):
+          homeserver.succeed(
+              "/run/current-system/specialisation/running/bin/switch-to-configuration test >&2"
+          )
+
+          homeserver.wait_for_unit("matrix-synapse.service")
+          homeserver.wait_for_open_port(8008)
+
+      with subtest("ensure messages can be exchanged"):
+          client.succeed("do_test ${homeserverUrl} >&2")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/matrix/conduit.nix b/nixpkgs/nixos/tests/matrix/conduit.nix
new file mode 100644
index 000000000000..2b81c23598eb
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/conduit.nix
@@ -0,0 +1,97 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  let
+    name = "conduit";
+  in
+  {
+    name = "matrix-conduit";
+
+    nodes = {
+      conduit = args: {
+        services.matrix-conduit = {
+          enable = true;
+          settings.global.server_name = name;
+          settings.global.allow_registration = true;
+          extraEnvironment.RUST_BACKTRACE = "yes";
+        };
+        services.nginx = {
+          enable = true;
+          virtualHosts.${name} = {
+            enableACME = false;
+            forceSSL = false;
+            enableSSL = false;
+
+            locations."/_matrix" = {
+              proxyPass = "http://[::1]:6167";
+            };
+          };
+        };
+        networking.firewall.allowedTCPPorts = [ 80 ];
+      };
+      client = { pkgs, ... }: {
+        environment.systemPackages = [
+          (
+            pkgs.writers.writePython3Bin "do_test"
+              { libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
+              import asyncio
+
+              from nio import AsyncClient
+
+
+              async def main() -> None:
+                  # Connect to conduit
+                  client = AsyncClient("http://conduit:80", "alice")
+
+                  # Register as user alice
+                  response = await client.register("alice", "my-secret-password")
+
+                  # Log in as user alice
+                  response = await client.login("my-secret-password")
+
+                  # Create a new room
+                  response = await client.room_create(federate=False)
+                  room_id = response.room_id
+
+                  # Join the room
+                  response = await client.join(room_id)
+
+                  # Send a message to the room
+                  response = await client.room_send(
+                      room_id=room_id,
+                      message_type="m.room.message",
+                      content={
+                          "msgtype": "m.text",
+                          "body": "Hello conduit!"
+                      }
+                  )
+
+                  # Sync responses
+                  response = await client.sync(timeout=30000)
+
+                  # Check the message was received by conduit
+                  last_message = response.rooms.join[room_id].timeline.events[-1].body
+                  assert last_message == "Hello conduit!"
+
+                  # Leave the room
+                  response = await client.room_leave(room_id)
+
+                  # Close the client
+                  await client.close()
+
+              asyncio.get_event_loop().run_until_complete(main())
+            ''
+          )
+        ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      with subtest("start conduit"):
+            conduit.wait_for_unit("conduit.service")
+            conduit.wait_for_open_port(80)
+
+      with subtest("ensure messages can be exchanged"):
+            client.succeed("do_test")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/matrix/dendrite.nix b/nixpkgs/nixos/tests/matrix/dendrite.nix
new file mode 100644
index 000000000000..82e71d912130
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/dendrite.nix
@@ -0,0 +1,101 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+    let
+      homeserverUrl = "http://homeserver:8008";
+
+      private_key = pkgs.runCommand "matrix_key.pem" {
+        buildInputs = [ pkgs.dendrite ];
+      } "generate-keys --private-key $out";
+    in
+      {
+        name = "dendrite";
+        meta = with pkgs.lib; {
+          maintainers = teams.matrix.members;
+        };
+
+        nodes = {
+          homeserver = { pkgs, ... }: {
+            services.dendrite = {
+              enable = true;
+              loadCredential = [ "test_private_key:${private_key}" ];
+              openRegistration = true;
+              settings = {
+                global.server_name = "test-dendrite-server.com";
+                global.private_key = "$CREDENTIALS_DIRECTORY/test_private_key";
+                client_api.registration_disabled = false;
+              };
+            };
+
+            networking.firewall.allowedTCPPorts = [ 8008 ];
+          };
+
+          client = { pkgs, ... }: {
+            environment.systemPackages = [
+              (
+                pkgs.writers.writePython3Bin "do_test"
+                  { libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
+                  import asyncio
+
+                  from nio import AsyncClient
+
+
+                  async def main() -> None:
+                      # Connect to dendrite
+                      client = AsyncClient("http://homeserver:8008", "alice")
+
+                      # Register as user alice
+                      response = await client.register("alice", "my-secret-password")
+
+                      # Log in as user alice
+                      response = await client.login("my-secret-password")
+
+                      # Create a new room
+                      response = await client.room_create(federate=False)
+                      room_id = response.room_id
+
+                      # Join the room
+                      response = await client.join(room_id)
+
+                      # Send a message to the room
+                      response = await client.room_send(
+                          room_id=room_id,
+                          message_type="m.room.message",
+                          content={
+                              "msgtype": "m.text",
+                              "body": "Hello world!"
+                          }
+                      )
+
+                      # Sync responses
+                      response = await client.sync(timeout=30000)
+
+                      # Check the message was received by dendrite
+                      last_message = response.rooms.join[room_id].timeline.events[-1].body
+                      assert last_message == "Hello world!"
+
+                      # Leave the room
+                      response = await client.room_leave(room_id)
+
+                      # Close the client
+                      await client.close()
+
+                  asyncio.get_event_loop().run_until_complete(main())
+                ''
+              )
+            ];
+          };
+        };
+
+        testScript = ''
+          start_all()
+
+          with subtest("start the homeserver"):
+              homeserver.wait_for_unit("dendrite.service")
+              homeserver.wait_for_open_port(8008)
+
+          with subtest("ensure messages can be exchanged"):
+              client.succeed("do_test")
+        '';
+
+      }
+)
diff --git a/nixpkgs/nixos/tests/matrix/mjolnir.nix b/nixpkgs/nixos/tests/matrix/mjolnir.nix
new file mode 100644
index 000000000000..8a888b17a3d7
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/mjolnir.nix
@@ -0,0 +1,176 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+  let
+    # Set up SSL certs for Synapse to be happy.
+    runWithOpenSSL = file: cmd: pkgs.runCommand file
+      {
+        buildInputs = [ pkgs.openssl ];
+      }
+      cmd;
+
+    ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+    ca_pem = runWithOpenSSL "ca.pem" ''
+      openssl req \
+        -x509 -new -nodes -key ${ca_key} \
+        -days 10000 -out $out -subj "/CN=snakeoil-ca"
+    '';
+    key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
+    csr = runWithOpenSSL "matrix.csr" ''
+      openssl req \
+         -new -key ${key} \
+         -out $out -subj "/CN=localhost" \
+    '';
+    cert = runWithOpenSSL "matrix_cert.pem" ''
+      openssl x509 \
+        -req -in ${csr} \
+        -CA ${ca_pem} -CAkey ${ca_key} \
+        -CAcreateserial -out $out \
+        -days 365
+    '';
+  in
+  {
+    name = "mjolnir";
+    meta = with pkgs.lib; {
+      maintainers = teams.matrix.members;
+    };
+
+    nodes = {
+      homeserver = { pkgs, ... }: {
+        services.matrix-synapse = {
+          enable = true;
+          settings = {
+            database.name = "sqlite3";
+            tls_certificate_path = "${cert}";
+            tls_private_key_path = "${key}";
+            enable_registration = true;
+            enable_registration_without_verification = true;
+            registration_shared_secret = "supersecret-registration";
+
+            listeners = [ {
+              # The default but tls=false
+              bind_addresses = [
+                "0.0.0.0"
+              ];
+              port = 8448;
+              resources = [ {
+                compress = true;
+                names = [ "client" ];
+              } {
+                compress = false;
+                names = [ "federation" ];
+              } ];
+              tls = false;
+              type = "http";
+              x_forwarded = false;
+            } ];
+          };
+        };
+
+        networking.firewall.allowedTCPPorts = [ 8448 ];
+
+        environment.systemPackages = [
+          (pkgs.writeShellScriptBin "register_mjolnir_user" ''
+            exec ${pkgs.matrix-synapse}/bin/register_new_matrix_user \
+              -u mjolnir \
+              -p mjolnir-password \
+              --admin \
+              --shared-secret supersecret-registration \
+              http://localhost:8448
+          ''
+          )
+          (pkgs.writeShellScriptBin "register_moderator_user" ''
+            exec ${pkgs.matrix-synapse}/bin/register_new_matrix_user \
+              -u moderator \
+              -p moderator-password \
+              --no-admin \
+              --shared-secret supersecret-registration \
+              http://localhost:8448
+          ''
+          )
+        ];
+      };
+
+      mjolnir = { pkgs, ... }: {
+        services.mjolnir = {
+          enable = true;
+          homeserverUrl = "http://homeserver:8448";
+          pantalaimon = {
+            enable = true;
+            username = "mjolnir";
+            passwordFile = pkgs.writeText "password.txt" "mjolnir-password";
+            # otherwise mjolnir tries to connect to ::1, which is not listened by pantalaimon
+            options.listenAddress = "127.0.0.1";
+          };
+          managementRoom = "#moderators:homeserver";
+        };
+      };
+
+      client = { pkgs, ... }: {
+        environment.systemPackages = [
+          (pkgs.writers.writePython3Bin "create_management_room_and_invite_mjolnir"
+            { libraries = with pkgs.python3Packages; [
+                matrix-nio
+              ] ++ matrix-nio.optional-dependencies.e2e;
+            } ''
+            import asyncio
+
+            from nio import (
+                AsyncClient,
+                EnableEncryptionBuilder
+            )
+
+
+            async def main() -> None:
+                client = AsyncClient("http://homeserver:8448", "moderator")
+
+                await client.login("moderator-password")
+
+                room = await client.room_create(
+                    name="Moderators",
+                    alias="moderators",
+                    initial_state=[EnableEncryptionBuilder().as_dict()],
+                )
+
+                await client.join(room.room_id)
+                await client.room_invite(room.room_id, "@mjolnir:homeserver")
+
+            asyncio.run(main())
+          ''
+          )
+        ];
+      };
+    };
+
+    testScript = ''
+      with subtest("start homeserver"):
+        homeserver.start()
+
+        homeserver.wait_for_unit("matrix-synapse.service")
+        homeserver.wait_until_succeeds("curl --fail -L http://localhost:8448/")
+
+      with subtest("register users"):
+        # register mjolnir user
+        homeserver.succeed("register_mjolnir_user")
+        # register moderator user
+        homeserver.succeed("register_moderator_user")
+
+      with subtest("start mjolnir"):
+        mjolnir.start()
+
+        # wait for pantalaimon to be ready
+        mjolnir.wait_for_unit("pantalaimon-mjolnir.service")
+        mjolnir.wait_for_unit("mjolnir.service")
+
+        mjolnir.wait_until_succeeds("curl --fail -L http://localhost:8009/")
+
+      with subtest("ensure mjolnir can be invited to the management room"):
+        client.start()
+
+        client.wait_until_succeeds("curl --fail -L http://homeserver:8448/")
+
+        client.succeed("create_management_room_and_invite_mjolnir")
+
+        mjolnir.wait_for_console_text("Startup complete. Now monitoring rooms")
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/matrix/pantalaimon.nix b/nixpkgs/nixos/tests/matrix/pantalaimon.nix
new file mode 100644
index 000000000000..b5d649e6517a
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/pantalaimon.nix
@@ -0,0 +1,88 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+  let
+    pantalaimonInstanceName = "testing";
+
+    # Set up SSL certs for Synapse to be happy.
+    runWithOpenSSL = file: cmd: pkgs.runCommand file
+      {
+        buildInputs = [ pkgs.openssl ];
+      }
+      cmd;
+
+    ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+    ca_pem = runWithOpenSSL "ca.pem" ''
+      openssl req \
+        -x509 -new -nodes -key ${ca_key} \
+        -days 10000 -out $out -subj "/CN=snakeoil-ca"
+    '';
+    key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
+    csr = runWithOpenSSL "matrix.csr" ''
+      openssl req \
+         -new -key ${key} \
+         -out $out -subj "/CN=localhost" \
+    '';
+    cert = runWithOpenSSL "matrix_cert.pem" ''
+      openssl x509 \
+        -req -in ${csr} \
+        -CA ${ca_pem} -CAkey ${ca_key} \
+        -CAcreateserial -out $out \
+        -days 365
+    '';
+  in
+  {
+    name = "pantalaimon";
+    meta = with pkgs.lib; {
+      maintainers = teams.matrix.members;
+    };
+
+    nodes.machine = { pkgs, ... }: {
+      services.pantalaimon-headless.instances.${pantalaimonInstanceName} = {
+        homeserver = "https://localhost:8448";
+        listenAddress = "0.0.0.0";
+        listenPort = 8888;
+        logLevel = "debug";
+        ssl = false;
+      };
+
+      services.matrix-synapse = {
+        enable = true;
+        settings = {
+          listeners = [ {
+            port = 8448;
+            bind_addresses = [
+              "127.0.0.1"
+              "::1"
+            ];
+            type = "http";
+            tls = true;
+            x_forwarded = false;
+            resources = [ {
+              names = [
+                "client"
+              ];
+              compress = true;
+            } {
+              names = [
+                "federation"
+              ];
+              compress = false;
+            } ];
+          } ];
+          database.name = "sqlite3";
+          tls_certificate_path = "${cert}";
+          tls_private_key_path = "${key}";
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("pantalaimon-${pantalaimonInstanceName}.service")
+      machine.wait_for_unit("matrix-synapse.service")
+      machine.wait_until_succeeds(
+          "curl --fail -L http://localhost:8888/"
+      )
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/matrix/synapse-workers.nix b/nixpkgs/nixos/tests/matrix/synapse-workers.nix
new file mode 100644
index 000000000000..e90301aeae9e
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/synapse-workers.nix
@@ -0,0 +1,50 @@
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "matrix-synapse-workers";
+  meta = with pkgs.lib; {
+    maintainers = teams.matrix.members;
+  };
+
+  nodes = {
+    homeserver =
+      { pkgs
+      , nodes
+      , ...
+      }: {
+        services.postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "synapse-init.sql" ''
+            CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+            CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+            TEMPLATE template0
+            LC_COLLATE = "C"
+            LC_CTYPE = "C";
+          '';
+        };
+
+        services.matrix-synapse = {
+          enable = true;
+          settings = {
+            database = {
+              name = "psycopg2";
+              args.password = "synapse";
+            };
+            enable_registration = true;
+            enable_registration_without_verification = true;
+
+            federation_sender_instances = [ "federation_sender" ];
+          };
+          configureRedisLocally = true;
+          workers = {
+            "federation_sender" = { };
+          };
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+
+    homeserver.wait_for_unit("matrix-synapse.service");
+    homeserver.wait_for_unit("matrix-synapse-worker-federation_sender.service");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/matrix/synapse.nix b/nixpkgs/nixos/tests/matrix/synapse.nix
new file mode 100644
index 000000000000..8c10a575ffbd
--- /dev/null
+++ b/nixpkgs/nixos/tests/matrix/synapse.nix
@@ -0,0 +1,218 @@
+import ../make-test-python.nix ({ pkgs, ... } : let
+
+  ca_key = mailerCerts.ca.key;
+  ca_pem = mailerCerts.ca.cert;
+
+  bundle = pkgs.runCommand "bundle" {
+    nativeBuildInputs = [ pkgs.minica ];
+  } ''
+    minica -ca-cert ${ca_pem} -ca-key ${ca_key} \
+      -domains localhost
+    install -Dm444 -t $out localhost/{key,cert}.pem
+  '';
+
+  mailerCerts = import ../common/acme/server/snakeoil-certs.nix;
+  mailerDomain = mailerCerts.domain;
+  registrationSharedSecret = "unsecure123";
+  testUser = "alice";
+  testPassword = "alicealice";
+  testEmail = "alice@example.com";
+
+  listeners = [ {
+    port = 8448;
+    bind_addresses = [
+      "127.0.0.1"
+      "::1"
+    ];
+    type = "http";
+    tls = true;
+    x_forwarded = false;
+    resources = [ {
+      names = [
+        "client"
+      ];
+      compress = true;
+    } {
+      names = [
+        "federation"
+      ];
+      compress = false;
+    } ];
+  } ];
+
+in {
+
+  name = "matrix-synapse";
+  meta = with pkgs.lib; {
+    maintainers = teams.matrix.members;
+  };
+
+  nodes = {
+    # Since 0.33.0, matrix-synapse doesn't allow underscores in server names
+    serverpostgres = { pkgs, nodes, config, ... }: let
+      mailserverIP = nodes.mailserver.config.networking.primaryIPAddress;
+    in
+    {
+      services.matrix-synapse = {
+        enable = true;
+        settings = {
+          inherit listeners;
+          database = {
+            name = "psycopg2";
+            args.password = "synapse";
+          };
+          redis = {
+            enabled = true;
+            host = "localhost";
+            port = config.services.redis.servers.matrix-synapse.port;
+          };
+          tls_certificate_path = "${bundle}/cert.pem";
+          tls_private_key_path = "${bundle}/key.pem";
+          registration_shared_secret = registrationSharedSecret;
+          public_baseurl = "https://example.com";
+          email = {
+            smtp_host = mailerDomain;
+            smtp_port = 25;
+            require_transport_security = true;
+            notif_from = "matrix <matrix@${mailerDomain}>";
+            app_name = "Matrix";
+          };
+        };
+      };
+      services.postgresql = {
+        enable = true;
+
+        # The database name and user are configured by the following options:
+        #   - services.matrix-synapse.database_name
+        #   - services.matrix-synapse.database_user
+        #
+        # The values used here represent the default values of the module.
+        initialScript = pkgs.writeText "synapse-init.sql" ''
+          CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+          CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+            TEMPLATE template0
+            LC_COLLATE = "C"
+            LC_CTYPE = "C";
+        '';
+      };
+
+      services.redis.servers.matrix-synapse = {
+        enable = true;
+        port = 6380;
+      };
+
+      networking.extraHosts = ''
+        ${mailserverIP} ${mailerDomain}
+      '';
+
+      security.pki.certificateFiles = [
+        mailerCerts.ca.cert ca_pem
+      ];
+
+      environment.systemPackages = let
+        sendTestMailStarttls = pkgs.writeScriptBin "send-testmail-starttls" ''
+          #!${pkgs.python3.interpreter}
+          import smtplib
+          import ssl
+
+          ctx = ssl.create_default_context()
+
+          with smtplib.SMTP('${mailerDomain}') as smtp:
+            smtp.ehlo()
+            smtp.starttls(context=ctx)
+            smtp.ehlo()
+            smtp.sendmail('matrix@${mailerDomain}', '${testEmail}', 'Subject: Test STARTTLS\n\nTest data.')
+            smtp.quit()
+         '';
+
+        obtainTokenAndRegisterEmail = let
+          # adding the email through the API is quite complicated as it involves more than one step and some
+          # client-side calculation
+          insertEmailForAlice = pkgs.writeText "alice-email.sql" ''
+            INSERT INTO user_threepids (user_id, medium, address, validated_at, added_at) VALUES ('${testUser}@serverpostgres', 'email', '${testEmail}', '1629149927271', '1629149927270');
+          '';
+        in
+        pkgs.writeScriptBin "obtain-token-and-register-email" ''
+          #!${pkgs.runtimeShell}
+          set -o errexit
+          set -o pipefail
+          set -o nounset
+          su postgres -c "psql -d matrix-synapse -f ${insertEmailForAlice}"
+          curl --fail -XPOST 'https://localhost:8448/_matrix/client/r0/account/password/email/requestToken' -d '{"email":"${testEmail}","client_secret":"foobar","send_attempt":1}' -v
+        '';
+        in [ sendTestMailStarttls pkgs.matrix-synapse obtainTokenAndRegisterEmail ];
+    };
+
+    # test mail delivery
+    mailserver = args: let
+    in
+    {
+      security.pki.certificateFiles = [
+        mailerCerts.ca.cert
+      ];
+
+      networking.firewall.enable = false;
+
+      services.postfix = {
+        enable = true;
+        hostname = "${mailerDomain}";
+        # open relay for subnet
+        networksStyle = "subnet";
+        enableSubmission = true;
+        tlsTrustedAuthorities = "${mailerCerts.ca.cert}";
+        sslCert = "${mailerCerts.${mailerDomain}.cert}";
+        sslKey = "${mailerCerts.${mailerDomain}.key}";
+
+        # blackhole transport
+        transport = "example.com discard:silently";
+
+        config = {
+          debug_peer_level = "10";
+          smtpd_relay_restrictions = [
+            "permit_mynetworks" "reject_unauth_destination"
+          ];
+
+          # disable obsolete protocols, something old versions of twisted are still using
+          smtpd_tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtp_tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtpd_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+          smtp_tls_mandatory_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
+        };
+      };
+    };
+
+    serversqlite = args: {
+      services.matrix-synapse = {
+        enable = true;
+        settings = {
+          inherit listeners;
+          database.name = "sqlite3";
+          tls_certificate_path = "${bundle}/cert.pem";
+          tls_private_key_path = "${bundle}/key.pem";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    mailserver.wait_for_unit("postfix.service")
+    serverpostgres.succeed("send-testmail-starttls")
+    serverpostgres.wait_for_unit("matrix-synapse.service")
+    serverpostgres.wait_until_succeeds(
+        "curl --fail -L --cacert ${ca_pem} https://localhost:8448/"
+    )
+    serverpostgres.wait_until_succeeds(
+        "journalctl -u matrix-synapse.service | grep -q 'Connected to redis'"
+    )
+    serverpostgres.require_unit_state("postgresql.service")
+    serverpostgres.succeed("REQUESTS_CA_BUNDLE=${ca_pem} register_new_matrix_user -u ${testUser} -p ${testPassword} -a -k ${registrationSharedSecret} https://localhost:8448/")
+    serverpostgres.succeed("obtain-token-and-register-email")
+    serversqlite.wait_for_unit("matrix-synapse.service")
+    serversqlite.wait_until_succeeds(
+        "curl --fail -L --cacert ${ca_pem} https://localhost:8448/"
+    )
+    serversqlite.succeed("[ -e /var/lib/matrix-synapse/homeserver.db ]")
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/mattermost.nix b/nixpkgs/nixos/tests/mattermost.nix
new file mode 100644
index 000000000000..e11201f05357
--- /dev/null
+++ b/nixpkgs/nixos/tests/mattermost.nix
@@ -0,0 +1,140 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  host = "smoke.test";
+  port = "8065";
+  url = "http://${host}:${port}";
+  siteName = "NixOS Smoke Tests, Inc.";
+
+  makeMattermost = mattermostConfig:
+    { config, ... }: {
+      environment.systemPackages = [
+        pkgs.mattermost
+        pkgs.curl
+        pkgs.jq
+      ];
+      networking.hosts = {
+        "127.0.0.1" = [ host ];
+      };
+      services.mattermost = lib.recursiveUpdate {
+        enable = true;
+        inherit siteName;
+        listenAddress = "0.0.0.0:${port}";
+        siteUrl = url;
+        extraConfig = {
+          SupportSettings.AboutLink = "https://nixos.org";
+        };
+      } mattermostConfig;
+    };
+in
+{
+  name = "mattermost";
+
+  nodes = {
+    mutable = makeMattermost {
+      mutableConfig = true;
+      extraConfig.SupportSettings.HelpLink = "https://search.nixos.org";
+    };
+    mostlyMutable = makeMattermost {
+      mutableConfig = true;
+      preferNixConfig = true;
+      plugins = let
+        mattermostDemoPlugin = pkgs.fetchurl {
+          url = "https://github.com/mattermost/mattermost-plugin-demo/releases/download/v0.9.0/com.mattermost.demo-plugin-0.9.0.tar.gz";
+          sha256 = "1h4qi34gcxcx63z8wiqcf2aaywmvv8lys5g8gvsk13kkqhlmag25";
+        };
+      in [
+        mattermostDemoPlugin
+      ];
+    };
+    immutable = makeMattermost {
+      mutableConfig = false;
+      extraConfig.SupportSettings.HelpLink = "https://search.nixos.org";
+    };
+    environmentFile = makeMattermost {
+      mutableConfig = false;
+      extraConfig.SupportSettings.AboutLink = "https://example.org";
+      environmentFile = pkgs.writeText "mattermost-env" ''
+        MM_SUPPORTSETTINGS_ABOUTLINK=https://nixos.org
+      '';
+    };
+  };
+
+  testScript = let
+    expectConfig = jqExpression: pkgs.writeShellScript "expect-config" ''
+      set -euo pipefail
+      echo "Expecting config to match: "${lib.escapeShellArg jqExpression} >&2
+      curl ${lib.escapeShellArg url} >/dev/null
+      config="$(curl ${lib.escapeShellArg "${url}/api/v4/config/client?format=old"})"
+      echo "Config: $(echo "$config" | ${pkgs.jq}/bin/jq)" >&2
+      [[ "$(echo "$config" | ${pkgs.jq}/bin/jq -r ${lib.escapeShellArg ".SiteName == $siteName and .Version == ($mattermostName / $sep)[-1] and (${jqExpression})"} --arg siteName ${lib.escapeShellArg siteName} --arg mattermostName ${lib.escapeShellArg pkgs.mattermost.name} --arg sep '-')" = "true" ]]
+    '';
+
+    setConfig = jqExpression: pkgs.writeShellScript "set-config" ''
+      set -euo pipefail
+      mattermostConfig=/var/lib/mattermost/config/config.json
+      newConfig="$(${pkgs.jq}/bin/jq -r ${lib.escapeShellArg jqExpression} $mattermostConfig)"
+      rm -f $mattermostConfig
+      echo "$newConfig" > "$mattermostConfig"
+    '';
+
+  in
+  ''
+    start_all()
+
+    ## Mutable node tests ##
+    mutable.wait_for_unit("mattermost.service")
+    mutable.wait_for_open_port(8065)
+
+    # Get the initial config
+    mutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
+
+    # Edit the config
+    mutable.succeed("${setConfig ''.SupportSettings.AboutLink = "https://mattermost.com"''}")
+    mutable.succeed("${setConfig ''.SupportSettings.HelpLink = "https://nixos.org/nixos/manual"''}")
+    mutable.systemctl("restart mattermost.service")
+    mutable.wait_for_open_port(8065)
+
+    # AboutLink and HelpLink should be changed
+    mutable.succeed("${expectConfig ''.AboutLink == "https://mattermost.com" and .HelpLink == "https://nixos.org/nixos/manual"''}")
+
+    ## Mostly mutable node tests ##
+    mostlyMutable.wait_for_unit("mattermost.service")
+    mostlyMutable.wait_for_open_port(8065)
+
+    # Get the initial config
+    mostlyMutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org"''}")
+
+    # Edit the config
+    mostlyMutable.succeed("${setConfig ''.SupportSettings.AboutLink = "https://mattermost.com"''}")
+    mostlyMutable.succeed("${setConfig ''.SupportSettings.HelpLink = "https://nixos.org/nixos/manual"''}")
+    mostlyMutable.systemctl("restart mattermost.service")
+    mostlyMutable.wait_for_open_port(8065)
+
+    # AboutLink should be overridden by NixOS configuration; HelpLink should be what we set above
+    mostlyMutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://nixos.org/nixos/manual"''}")
+
+    ## Immutable node tests ##
+    immutable.wait_for_unit("mattermost.service")
+    immutable.wait_for_open_port(8065)
+
+    # Get the initial config
+    immutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
+
+    # Edit the config
+    immutable.succeed("${setConfig ''.SupportSettings.AboutLink = "https://mattermost.com"''}")
+    immutable.succeed("${setConfig ''.SupportSettings.HelpLink = "https://nixos.org/nixos/manual"''}")
+    immutable.systemctl("restart mattermost.service")
+    immutable.wait_for_open_port(8065)
+
+    # Our edits should be ignored on restart
+    immutable.succeed("${expectConfig ''.AboutLink == "https://nixos.org" and .HelpLink == "https://search.nixos.org"''}")
+
+
+    ## Environment File node tests ##
+    environmentFile.wait_for_unit("mattermost.service")
+    environmentFile.wait_for_open_port(8065)
+
+    # Settings in the environment file should override settings set otherwise
+    environmentFile.succeed("${expectConfig ''.AboutLink == "https://nixos.org"''}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mediamtx.nix b/nixpkgs/nixos/tests/mediamtx.nix
new file mode 100644
index 000000000000..8cacd02631d9
--- /dev/null
+++ b/nixpkgs/nixos/tests/mediamtx.nix
@@ -0,0 +1,57 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+  name = "mediamtx";
+  meta.maintainers = with lib.maintainers; [ fpletz ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.mediamtx = {
+        enable = true;
+        settings = {
+          metrics = true;
+          paths.all.source = "publisher";
+        };
+      };
+
+      systemd.services.rtmp-publish = {
+        description = "Publish an RTMP stream to mediamtx";
+        after = [ "mediamtx.service" ];
+        bindsTo = [ "mediamtx.service" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          Restart = "on-failure";
+          RestartSec = "1s";
+          TimeoutStartSec = "10s";
+          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -re -f lavfi -i smptebars=size=800x600:rate=10 -c libx264 -f flv rtmp://localhost:1935/test";
+        };
+      };
+
+      systemd.services.rtmp-receive = {
+        description = "Receive an RTMP stream from mediamtx";
+        after = [ "rtmp-publish.service" ];
+        bindsTo = [ "rtmp-publish.service" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          Restart = "on-failure";
+          RestartSec = "1s";
+          TimeoutStartSec = "10s";
+          ExecStart = "${lib.getBin pkgs.ffmpeg-headless}/bin/ffmpeg -y -re -i rtmp://localhost:1935/test -f flv /dev/null";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("mediamtx.service")
+    machine.wait_for_unit("rtmp-publish.service")
+    machine.wait_for_unit("rtmp-receive.service")
+    machine.wait_for_open_port(9998)
+    machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"publish\".*1$'")
+    machine.succeed("curl http://localhost:9998/metrics | grep '^rtmp_conns.*state=\"read\".*1$'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mediatomb.nix b/nixpkgs/nixos/tests/mediatomb.nix
new file mode 100644
index 000000000000..9c84aa3e92a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/mediatomb.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix {
+  name = "mediatomb";
+
+  nodes = {
+    server = {
+      services.mediatomb = {
+        enable = true;
+        serverName = "Gerbera";
+        interface = "eth1";
+        openFirewall = true;
+        mediaDirectories = [
+          {
+            path = "/var/lib/gerbera/pictures";
+            recursive = false;
+            hidden-files = false;
+          }
+          {
+            path = "/var/lib/gerbera/audio";
+            recursive = true;
+            hidden-files = false;
+          }
+        ];
+      };
+      systemd.tmpfiles.rules = [
+        "d /var/lib/gerbera/pictures 0770 mediatomb mediatomb"
+        "d /var/lib/gerbera/audio 0770 mediatomb mediatomb"
+      ];
+    };
+
+    client = {};
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("mediatomb")
+    server.wait_until_succeeds("nc -z 192.168.1.2 49152")
+    server.succeed("curl -v --fail http://server:49152/")
+
+    client.wait_for_unit("multi-user.target")
+    page = client.succeed("curl -v --fail http://server:49152/")
+    assert "Gerbera" in page and "MediaTomb" not in page
+  '';
+}
diff --git a/nixpkgs/nixos/tests/mediawiki.nix b/nixpkgs/nixos/tests/mediawiki.nix
new file mode 100644
index 000000000000..e30cc55ff616
--- /dev/null
+++ b/nixpkgs/nixos/tests/mediawiki.nix
@@ -0,0 +1,93 @@
+{
+  system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+}:
+
+let
+  shared = {
+    services.mediawiki.enable = true;
+    services.mediawiki.httpd.virtualHost.hostName = "localhost";
+    services.mediawiki.httpd.virtualHost.adminAddr = "root@example.com";
+    services.mediawiki.passwordFile = pkgs.writeText "password" "correcthorsebatterystaple";
+    services.mediawiki.extensions = {
+      Matomo = pkgs.fetchzip {
+        url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz";
+        sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b";
+      };
+      ParserFunctions = null;
+    };
+  };
+
+  testLib = import ../lib/testing-python.nix {
+    inherit system pkgs;
+    extraConfigurations = [ shared ];
+  };
+in
+{
+  mysql = testLib.makeTest {
+    name = "mediawiki-mysql";
+    nodes.machine = {
+      services.mediawiki.database.type = "mysql";
+    };
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("phpfpm-mediawiki.service")
+
+      page = machine.succeed("curl -fL http://localhost/")
+      assert "MediaWiki has been installed" in page
+    '';
+  };
+
+  postgresql = testLib.makeTest {
+    name = "mediawiki-postgres";
+    nodes.machine = {
+      services.mediawiki.database.type = "postgres";
+    };
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("phpfpm-mediawiki.service")
+
+      page = machine.succeed("curl -fL http://localhost/")
+      assert "MediaWiki has been installed" in page
+    '';
+  };
+
+  nohttpd = testLib.makeTest {
+    name = "mediawiki-nohttpd";
+    nodes.machine = {
+      services.mediawiki.webserver = "none";
+    };
+    testScript = { nodes, ... }: ''
+      start_all()
+      machine.wait_for_unit("phpfpm-mediawiki.service")
+      env = (
+        "SCRIPT_NAME=/index.php",
+        "SCRIPT_FILENAME=${nodes.machine.services.mediawiki.finalPackage}/share/mediawiki/index.php",
+        "REMOTE_ADDR=127.0.0.1",
+        'QUERY_STRING=title=Main_Page',
+        "REQUEST_METHOD=GET",
+      );
+      page = machine.succeed(f"{' '.join(env)} ${pkgs.fcgi}/bin/cgi-fcgi -bind -connect ${nodes.machine.services.phpfpm.pools.mediawiki.socket}")
+      assert "MediaWiki has been installed" in page, f"no 'MediaWiki has been installed' in:\n{page}"
+    '';
+  };
+
+  nginx = testLib.makeTest {
+    name = "mediawiki-nginx";
+    nodes.machine = {
+      services.mediawiki.webserver = "nginx";
+    };
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("phpfpm-mediawiki.service")
+      machine.wait_for_unit("nginx.service")
+
+      page = machine.succeed("curl -fL http://localhost/")
+      assert "MediaWiki has been installed" in page
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/meilisearch.nix b/nixpkgs/nixos/tests/meilisearch.nix
new file mode 100644
index 000000000000..c31dcb0559db
--- /dev/null
+++ b/nixpkgs/nixos/tests/meilisearch.nix
@@ -0,0 +1,61 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    listenAddress = "127.0.0.1";
+    listenPort = 7700;
+    apiUrl = "http://${listenAddress}:${toString listenPort}";
+    uid = "movies";
+    indexJSON = pkgs.writeText "index.json" (builtins.toJSON { inherit uid; });
+    moviesJSON = pkgs.fetchurl {
+      url = "https://github.com/meilisearch/meilisearch/raw/v0.23.1/datasets/movies/movies.json";
+      sha256 = "1r3srld63dpmg9yrmysm6xl175661j5cspi93mk5q2wf8xwn50c5";
+    };
+  in {
+    name = "meilisearch";
+    meta.maintainers = with lib.maintainers; [ Br1ght0ne ];
+
+    nodes.machine = { ... }: {
+      environment.systemPackages = with pkgs; [ curl jq ];
+      services.meilisearch = {
+        enable = true;
+        inherit listenAddress listenPort;
+      };
+    };
+
+    testScript = ''
+      import json
+
+      start_all()
+
+      machine.wait_for_unit("meilisearch")
+      machine.wait_for_open_port(7700)
+
+      with subtest("check version"):
+          version = json.loads(machine.succeed("curl ${apiUrl}/version"))
+          assert version["pkgVersion"] == "${pkgs.meilisearch.version}"
+
+      with subtest("create index"):
+          machine.succeed(
+              "curl -X POST -H 'Content-Type: application/json' ${apiUrl}/indexes --data @${indexJSON}"
+          )
+          indexes = json.loads(machine.succeed("curl ${apiUrl}/indexes"))
+          assert indexes["total"] == 1, "index wasn't created"
+
+      with subtest("add documents"):
+          response = json.loads(
+              machine.succeed(
+                  "curl -X POST -H 'Content-Type: application/json' ${apiUrl}/indexes/${uid}/documents --data-binary @${moviesJSON}"
+              )
+          )
+          task_uid = response["taskUid"]
+          machine.wait_until_succeeds(
+              f"curl ${apiUrl}/tasks/{task_uid} | jq -e '.status == \"succeeded\"'"
+          )
+
+      with subtest("search"):
+          response = json.loads(
+              machine.succeed("curl ${apiUrl}/indexes/movies/search?q=hero")
+          )
+          print(response)
+          assert len(response["hits"]) >= 1, "no results found"
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/memcached.nix b/nixpkgs/nixos/tests/memcached.nix
new file mode 100644
index 000000000000..6549995110d7
--- /dev/null
+++ b/nixpkgs/nixos/tests/memcached.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "memcached";
+
+  nodes.machine = {
+    imports = [ ../modules/profiles/minimal.nix ];
+    services.memcached.enable = true;
+  };
+
+  testScript = let
+    testScript = pkgs.writers.writePython3 "test_memcache" {
+      libraries = with pkgs.python3Packages; [ memcached ];
+    } ''
+      import memcache
+      c = memcache.Client(['localhost:11211'])
+      c.set('key', 'value')
+      assert 'value' == c.get('key')
+    '';
+  in ''
+    machine.start()
+    machine.wait_for_unit("memcached.service")
+    machine.wait_for_open_port(11211)
+    machine.succeed("${testScript}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/merecat.nix b/nixpkgs/nixos/tests/merecat.nix
new file mode 100644
index 000000000000..9d8f66165ee9
--- /dev/null
+++ b/nixpkgs/nixos/tests/merecat.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "merecat";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.merecat = {
+      enable = true;
+      settings = {
+        hostname = "localhost";
+        virtual-host = true;
+        directory = toString (pkgs.runCommand "merecat-webdir" {} ''
+          mkdir -p $out/foo.localhost $out/bar.localhost
+          echo '<h1>Hello foo</h1>' > $out/foo.localhost/index.html
+          echo '<h1>Hello bar</h1>' > $out/bar.localhost/index.html
+        '');
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("merecat")
+    machine.wait_for_open_port(80)
+    machine.succeed("curl --fail foo.localhost | grep 'Hello foo'")
+    machine.succeed("curl --fail bar.localhost | grep 'Hello bar'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/metabase.nix b/nixpkgs/nixos/tests/metabase.nix
new file mode 100644
index 000000000000..1b25071902e9
--- /dev/null
+++ b/nixpkgs/nixos/tests/metabase.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "metabase";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.metabase.enable = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("metabase.service")
+    machine.wait_for_open_port(3000)
+    machine.wait_until_succeeds("curl -fL http://localhost:3000/setup | grep Metabase")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mimir.nix b/nixpkgs/nixos/tests/mimir.nix
new file mode 100644
index 000000000000..f1b30d261472
--- /dev/null
+++ b/nixpkgs/nixos/tests/mimir.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "mimir";
+  nodes = {
+    server = { ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      services.mimir.enable = true;
+      services.mimir.configuration = {
+        ingester.ring.replication_factor = 1;
+      };
+
+      services.telegraf.enable = true;
+      services.telegraf.extraConfig = {
+        agent.interval = "1s";
+        agent.flush_interval = "1s";
+        inputs.exec = {
+          commands = [
+            "${pkgs.coreutils}/bin/echo 'foo i=42i'"
+          ];
+          data_format = "influx";
+        };
+        outputs = {
+          http = {
+            # test remote write
+            url = "http://localhost:8080/api/v1/push";
+
+            # Data format to output.
+            data_format = "prometheusremotewrite";
+
+            headers = {
+              Content-Type = "application/x-protobuf";
+              Content-Encoding = "snappy";
+              X-Scope-OrgID = "nixos";
+              X-Prometheus-Remote-Write-Version = "0.1.0";
+            };
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("mimir.service")
+    server.wait_for_unit("telegraf.service")
+    server.wait_for_open_port(8080)
+    server.wait_until_succeeds(
+        "curl -H 'X-Scope-OrgID: nixos' http://127.0.0.1:8080/prometheus/api/v1/label/host/values | jq -r '.data[0]' | grep server"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mindustry.nix b/nixpkgs/nixos/tests/mindustry.nix
new file mode 100644
index 000000000000..b3f5423c601b
--- /dev/null
+++ b/nixpkgs/nixos/tests/mindustry.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "mindustry";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.mindustry ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("mindustry >&2 &")
+      machine.wait_for_window("Mindustry")
+      # Loading can take a while. Avoid wasting cycles on OCR during that time
+      machine.sleep(60)
+      machine.wait_for_text(r"(Play|Database|Editor|Mods|Settings|Quit)")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/minecraft-server.nix b/nixpkgs/nixos/tests/minecraft-server.nix
new file mode 100644
index 000000000000..6e733bb96c1c
--- /dev/null
+++ b/nixpkgs/nixos/tests/minecraft-server.nix
@@ -0,0 +1,40 @@
+let
+  seed = "2151901553968352745";
+  rcon-pass = "foobar";
+  rcon-port = 43000;
+in import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "minecraft-server";
+  meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
+
+  nodes.server = { ... }: {
+    environment.systemPackages = [ pkgs.mcrcon ];
+
+    nixpkgs.config.allowUnfree = true;
+
+    services.minecraft-server = {
+      declarative = true;
+      enable = true;
+      eula = true;
+      serverProperties = {
+        enable-rcon = true;
+        level-seed = seed;
+        level-type = "flat";
+        generate-structures = false;
+        online-mode = false;
+        "rcon.password" = rcon-pass;
+        "rcon.port" = rcon-port;
+      };
+    };
+
+    virtualisation.memorySize = 2047;
+  };
+
+  testScript = ''
+    server.wait_for_unit("minecraft-server")
+    server.wait_for_open_port(${toString rcon-port})
+    assert "${seed}" in server.succeed(
+        "mcrcon -H localhost -P ${toString rcon-port} -p '${rcon-pass}' -c 'seed'"
+    )
+    server.succeed("systemctl stop minecraft-server")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/minecraft.nix b/nixpkgs/nixos/tests/minecraft.nix
new file mode 100644
index 000000000000..1c34f04b4df2
--- /dev/null
+++ b/nixpkgs/nixos/tests/minecraft.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "minecraft";
+  meta = with lib.maintainers; { maintainers = [ nequissimus ]; };
+
+  nodes.client = { nodes, ... }:
+      let user = nodes.client.config.users.users.alice;
+      in {
+        imports = [ ./common/user-account.nix ./common/x11.nix ];
+
+        environment.systemPackages = [ pkgs.minecraft ];
+
+        nixpkgs.config.allowUnfree = true;
+
+        test-support.displayManager.auto.user = user.name;
+      };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+    let user = nodes.client.config.users.users.alice;
+    in ''
+      client.wait_for_x()
+      client.execute("su - alice -c minecraft-launcher >&2 &")
+      client.wait_for_text("Create a new Microsoft account")
+      client.sleep(10)
+      client.screenshot("launcher")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/minidlna.nix b/nixpkgs/nixos/tests/minidlna.nix
new file mode 100644
index 000000000000..32721819634e
--- /dev/null
+++ b/nixpkgs/nixos/tests/minidlna.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "minidlna";
+
+  nodes = {
+    server =
+      { ... }:
+      {
+        imports = [ ../modules/profiles/minimal.nix ];
+        services.minidlna.enable = true;
+        services.minidlna.openFirewall = true;
+        services.minidlna.settings = {
+          log_level = "error";
+          media_dir = [
+            "PV,/tmp/stuff"
+          ];
+          friendly_name = "rpi3";
+          root_container = "B";
+          notify_interval = 60;
+          album_art_names = [
+            "Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg"
+            "AlbumArt.jpg/albumart.jpg/Album.jpg/album.jpg"
+            "Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg"
+          ];
+        };
+      };
+    client = { ... }: { };
+  };
+
+  testScript =
+  ''
+    start_all()
+    server.succeed("mkdir -p /tmp/stuff && chown minidlna: /tmp/stuff")
+    server.wait_for_unit("minidlna")
+    server.wait_for_open_port(8200)
+    # requests must be made *by IP* to avoid triggering minidlna's
+    # DNS-rebinding protection
+    server.succeed("curl --fail http://$(getent ahostsv4 localhost | head -n1 | cut -f 1 -d ' '):8200/")
+    client.succeed("curl --fail http://$(getent ahostsv4 server | head -n1 | cut -f 1 -d ' '):8200/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/miniflux.nix b/nixpkgs/nixos/tests/miniflux.nix
new file mode 100644
index 000000000000..a3af53db0e7a
--- /dev/null
+++ b/nixpkgs/nixos/tests/miniflux.nix
@@ -0,0 +1,87 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  port = 3142;
+  username = "alice";
+  password = "correcthorsebatterystaple";
+  defaultPort = 8080;
+  defaultUsername = "admin";
+  defaultPassword = "password";
+  adminCredentialsFile = pkgs.writeText "admin-credentials" ''
+            ADMIN_USERNAME=${defaultUsername}
+            ADMIN_PASSWORD=${defaultPassword}
+          '';
+  customAdminCredentialsFile = pkgs.writeText "admin-credentials" ''
+            ADMIN_USERNAME=${username}
+            ADMIN_PASSWORD=${password}
+          '';
+
+in
+{
+  name = "miniflux";
+  meta.maintainers = [ ];
+
+  nodes = {
+    default =
+      { ... }:
+      {
+        security.apparmor.enable = true;
+        services.miniflux = {
+          enable = true;
+          inherit adminCredentialsFile;
+        };
+      };
+
+    withoutSudo =
+      { ... }:
+      {
+        security.apparmor.enable = true;
+        services.miniflux = {
+          enable = true;
+          inherit adminCredentialsFile;
+        };
+        security.sudo.enable = false;
+      };
+
+    customized =
+      { ... }:
+      {
+        security.apparmor.enable = true;
+        services.miniflux = {
+          enable = true;
+          config = {
+            CLEANUP_FREQUENCY = "48";
+            LISTEN_ADDR = "localhost:${toString port}";
+          };
+          adminCredentialsFile = customAdminCredentialsFile;
+        };
+      };
+  };
+  testScript = ''
+    start_all()
+
+    default.wait_for_unit("miniflux.service")
+    default.wait_for_open_port(${toString defaultPort})
+    default.succeed("curl --fail 'http://localhost:${toString defaultPort}/healthcheck' | grep OK")
+    default.succeed(
+        "curl 'http://localhost:${toString defaultPort}/v1/me' -u '${defaultUsername}:${defaultPassword}' -H Content-Type:application/json | grep '\"is_admin\":true'"
+    )
+    default.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""')
+
+    withoutSudo.wait_for_unit("miniflux.service")
+    withoutSudo.wait_for_open_port(${toString defaultPort})
+    withoutSudo.succeed("curl --fail 'http://localhost:${toString defaultPort}/healthcheck' | grep OK")
+    withoutSudo.succeed(
+        "curl 'http://localhost:${toString defaultPort}/v1/me' -u '${defaultUsername}:${defaultPassword}' -H Content-Type:application/json | grep '\"is_admin\":true'"
+    )
+    withoutSudo.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""')
+
+    customized.wait_for_unit("miniflux.service")
+    customized.wait_for_open_port(${toString port})
+    customized.succeed("curl --fail 'http://localhost:${toString port}/healthcheck' | grep OK")
+    customized.succeed(
+        "curl 'http://localhost:${toString port}/v1/me' -u '${username}:${password}' -H Content-Type:application/json | grep '\"is_admin\":true'"
+    )
+    customized.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/minio.nix b/nixpkgs/nixos/tests/minio.nix
new file mode 100644
index 000000000000..ece4864f771c
--- /dev/null
+++ b/nixpkgs/nixos/tests/minio.nix
@@ -0,0 +1,72 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    accessKey = "BKIKJAA5BMMU2RHO6IBB";
+    secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
+    minioPythonScript = pkgs.writeScript "minio-test.py" ''
+      #! ${pkgs.python3.withPackages(ps: [ ps.minio ])}/bin/python
+      import io
+      import os
+      from minio import Minio
+      minioClient = Minio('localhost:9000',
+                    access_key='${accessKey}',
+                    secret_key='${secretKey}',
+                    secure=False)
+      sio = io.BytesIO()
+      sio.write(b'Test from Python')
+      sio.seek(0, os.SEEK_END)
+      sio_len = sio.tell()
+      sio.seek(0)
+      minioClient.put_object('test-bucket', 'test.txt', sio, sio_len, content_type='text/plain')
+    '';
+    rootCredentialsFile = "/etc/nixos/minio-root-credentials";
+    credsPartial = pkgs.writeText "minio-credentials-partial" ''
+      MINIO_ROOT_USER=${accessKey}
+    '';
+    credsFull = pkgs.writeText "minio-credentials-full" ''
+      MINIO_ROOT_USER=${accessKey}
+      MINIO_ROOT_PASSWORD=${secretKey}
+    '';
+  in
+  {
+    name = "minio";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bachp ];
+    };
+
+    nodes = {
+      machine = { pkgs, ... }: {
+        services.minio = {
+          enable = true;
+          inherit rootCredentialsFile;
+        };
+        environment.systemPackages = [ pkgs.minio-client ];
+
+        # Minio requires at least 1GiB of free disk space to run.
+        virtualisation.diskSize = 4 * 1024;
+      };
+    };
+
+    testScript = ''
+      import time
+
+      start_all()
+      # simulate manually editing root credentials file
+      machine.wait_for_unit("multi-user.target")
+      machine.copy_from_host("${credsPartial}", "${rootCredentialsFile}")
+      time.sleep(3)
+      machine.copy_from_host("${credsFull}", "${rootCredentialsFile}")
+
+      machine.wait_for_unit("minio.service")
+      machine.wait_for_open_port(9000)
+
+      # Create a test bucket on the server
+      machine.succeed(
+          "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4"
+      )
+      machine.succeed("mc mb minio/test-bucket")
+      machine.succeed("${minioPythonScript}")
+      assert "test-bucket" in machine.succeed("mc ls minio")
+      assert "Test from Python" in machine.succeed("mc cat minio/test-bucket/test.txt")
+      machine.shutdown()
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/miriway.nix b/nixpkgs/nixos/tests/miriway.nix
new file mode 100644
index 000000000000..a0987d9fc41b
--- /dev/null
+++ b/nixpkgs/nixos/tests/miriway.nix
@@ -0,0 +1,125 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "miriway";
+
+  meta = {
+    maintainers = with lib.maintainers; [ OPNA2608 ];
+  };
+
+  nodes.machine = { config, ... }: {
+    imports = [
+      ./common/auto.nix
+      ./common/user-account.nix
+    ];
+
+    # Seems to very rarely get interrupted by oom-killer
+    virtualisation.memorySize = 2047;
+
+    test-support.displayManager.auto = {
+      enable = true;
+      user = "alice";
+    };
+
+    services.xserver = {
+      enable = true;
+      displayManager.defaultSession = lib.mkForce "miriway";
+    };
+
+    programs.miriway = {
+      enable = true;
+      config = ''
+        add-wayland-extensions=all
+        enable-x11=
+
+        ctrl-alt=t:foot --maximized
+        ctrl-alt=a:env WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= alacritty --option window.startup_mode=\"maximized\"
+
+        shell-component=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY
+
+        shell-component=foot --maximized
+      '';
+    };
+
+    environment = {
+      shellAliases = {
+        test-wayland = "wayland-info | tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok";
+        test-x11 = "glinfo | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok";
+      };
+
+      systemPackages = with pkgs; [
+        mesa-demos
+        wayland-utils
+        foot
+        alacritty
+      ];
+
+      # To help with OCR
+      etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
+        main = {
+          font = "inconsolata:size=16";
+        };
+        colors = rec {
+          foreground = "000000";
+          background = "ffffff";
+          regular2 = foreground;
+        };
+      };
+      etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
+        font = rec {
+          normal.family = "Inconsolata";
+          bold.family = normal.family;
+          italic.family = normal.family;
+          bold_italic.family = normal.family;
+          size = 16;
+        };
+        colors = rec {
+          primary = {
+            foreground = "0x000000";
+            background = "0xffffff";
+          };
+          normal = {
+            green = primary.foreground;
+          };
+        };
+      };
+    };
+
+    fonts.packages = [ pkgs.inconsolata ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+
+    # Wait for Miriway to complete startup
+    machine.wait_for_file("/run/user/1000/wayland-0")
+    machine.succeed("pgrep miriway-shell")
+    machine.screenshot("miriway_launched")
+
+    # Test Wayland
+    # We let Miriway start the first terminal, as we might get stuck if it's not ready to process the first keybind
+    # machine.send_key("ctrl-alt-t")
+    machine.wait_for_text("alice@machine")
+    machine.send_chars("test-wayland\n")
+    machine.wait_for_file("/tmp/test-wayland-exit-ok")
+    machine.copy_from_vm("/tmp/test-wayland.out")
+    machine.screenshot("foot_wayland_info")
+    # Only succeeds when a mouse is moved inside an interactive session?
+    # machine.send_chars("exit\n")
+    # machine.wait_until_fails("pgrep foot")
+    machine.succeed("pkill foot")
+
+    # Test XWayland
+    machine.send_key("ctrl-alt-a")
+    machine.wait_for_text("alice@machine")
+    machine.send_chars("test-x11\n")
+    machine.wait_for_file("/tmp/test-x11-exit-ok")
+    machine.copy_from_vm("/tmp/test-x11.out")
+    machine.screenshot("alacritty_glinfo")
+    # Only succeeds when a mouse is moved inside an interactive session?
+    # machine.send_chars("exit\n")
+    # machine.wait_until_fails("pgrep alacritty")
+    machine.succeed("pkill alacritty")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/misc.nix b/nixpkgs/nixos/tests/misc.nix
new file mode 100644
index 000000000000..e7842debba7a
--- /dev/null
+++ b/nixpkgs/nixos/tests/misc.nix
@@ -0,0 +1,164 @@
+# Miscellaneous small tests that don't warrant their own VM run.
+
+import ./make-test-python.nix ({ lib, pkgs, ...} : let
+  foo = pkgs.writeText "foo" "Hello World";
+in {
+  name = "misc";
+  meta.maintainers = with lib.maintainers; [ eelco ];
+
+  nodes.machine =
+    { lib, ... }:
+    { swapDevices = lib.mkOverride 0
+        [ { device = "/root/swapfile"; size = 128; } ];
+      environment.variables.EDITOR = lib.mkOverride 0 "emacs";
+      documentation.nixos.enable = lib.mkOverride 0 true;
+      systemd.tmpfiles.rules = [ "d /tmp 1777 root root 10d" ];
+      systemd.tmpfiles.settings."10-test"."/tmp/somefile".d = {};
+      virtualisation.fileSystems = { "/tmp2" =
+        { fsType = "tmpfs";
+          options = [ "mode=1777" "noauto" ];
+        };
+        # Tests https://discourse.nixos.org/t/how-to-make-a-derivations-executables-have-the-s-permission/8555
+        "/user-mount/point" = {
+          device = "/user-mount/source";
+          fsType = "none";
+          options = [ "bind" "rw" "user" "noauto" ];
+        };
+        "/user-mount/denied-point" = {
+          device = "/user-mount/denied-source";
+          fsType = "none";
+          options = [ "bind" "rw" "noauto" ];
+        };
+      };
+      systemd.automounts = lib.singleton
+        { wantedBy = [ "multi-user.target" ];
+          where = "/tmp2";
+        };
+      users.users.sybil = { isNormalUser = true; group = "wheel"; };
+      users.users.alice = { isNormalUser = true; };
+      security.sudo = { enable = true; wheelNeedsPassword = false; };
+      boot.kernel.sysctl."vm.swappiness" = 1;
+      boot.kernelParams = [ "vsyscall=emulate" ];
+      system.extraDependencies = [ foo ];
+    };
+
+  testScript =
+    ''
+      import json
+
+
+      def get_path_info(path):
+          result = machine.succeed(f"nix --option experimental-features nix-command path-info --json {path}")
+          parsed = json.loads(result)
+          return parsed
+
+
+      with subtest("nix-db"):
+          info = get_path_info("${foo}")
+          print(info)
+
+          if (
+              info[0]["narHash"]
+              != "sha256-BdMdnb/0eWy3EddjE83rdgzWWpQjfWPAj3zDIFMD3Ck="
+          ):
+              raise Exception("narHash not set")
+
+          if info[0]["narSize"] != 128:
+              raise Exception("narSize not set")
+
+      with subtest("nixos-version"):
+          machine.succeed("[ `nixos-version | wc -w` = 2 ]")
+
+      with subtest("nixos-rebuild"):
+          assert "NixOS module" in machine.succeed("nixos-rebuild --help")
+
+      with subtest("Sanity check for uid/gid assignment"):
+          assert "4" == machine.succeed("id -u messagebus").strip()
+          assert "4" == machine.succeed("id -g messagebus").strip()
+          assert "users:x:100:" == machine.succeed("getent group users").strip()
+
+      with subtest("Regression test for GMP aborts on QEMU."):
+          machine.succeed("expr 1 + 2")
+
+      with subtest("the swap file got created"):
+          machine.wait_for_unit("root-swapfile.swap")
+          machine.succeed("ls -l /root/swapfile | grep 134217728")
+
+      with subtest("whether kernel.poweroff_cmd is set"):
+          machine.succeed('[ -x "$(cat /proc/sys/kernel/poweroff_cmd)" ]')
+
+      with subtest("whether the io cgroupv2 controller is properly enabled"):
+          machine.succeed("grep -q '\\bio\\b' /sys/fs/cgroup/cgroup.controllers")
+
+      with subtest("whether we have a reboot record in wtmp"):
+          machine.shutdown
+          machine.wait_for_unit("multi-user.target")
+          machine.succeed("last | grep reboot >&2")
+
+      with subtest("whether we can override environment variables"):
+          machine.succeed('[ "$EDITOR" = emacs ]')
+
+      with subtest("whether hostname (and by extension nss_myhostname) works"):
+          assert "machine" == machine.succeed("hostname").strip()
+          assert "machine" == machine.succeed("hostname -s").strip()
+
+      with subtest("whether systemd-udevd automatically loads modules for our hardware"):
+          machine.succeed("systemctl start systemd-udev-settle.service")
+          machine.wait_for_unit("systemd-udev-settle.service")
+          assert "mousedev" in machine.succeed("lsmod")
+
+      with subtest("whether systemd-tmpfiles-clean works"):
+          machine.succeed(
+              "touch /tmp/foo", "systemctl start systemd-tmpfiles-clean", "[ -e /tmp/foo ]"
+          )
+          # move into the future
+          machine.succeed(
+              'date -s "@$(($(date +%s) + 1000000))"',
+              "systemctl start systemd-tmpfiles-clean",
+          )
+          machine.fail("[ -e /tmp/foo ]")
+
+      with subtest("whether systemd-tmpfiles settings works"):
+          machine.succeed("[ -e /tmp/somefile ]")
+
+      with subtest("whether automounting works"):
+          machine.fail("grep '/tmp2 tmpfs' /proc/mounts")
+          machine.succeed("touch /tmp2/x")
+          machine.succeed("grep '/tmp2 tmpfs' /proc/mounts")
+
+      with subtest(
+          "Whether mounting by a user is possible with the `user` option in fstab (#95444)"
+      ):
+          machine.succeed("mkdir -p /user-mount/source")
+          machine.succeed("touch /user-mount/source/file")
+          machine.succeed("chmod -R a+Xr /user-mount/source")
+          machine.succeed("mkdir /user-mount/point")
+          machine.succeed("chown alice:users /user-mount/point")
+          machine.succeed("su - alice -c 'mount /user-mount/point'")
+          machine.succeed("su - alice -c 'ls /user-mount/point/file'")
+      with subtest(
+          "Whether mounting by a user is denied without the `user` option in  fstab"
+      ):
+          machine.succeed("mkdir -p /user-mount/denied-source")
+          machine.succeed("touch /user-mount/denied-source/file")
+          machine.succeed("chmod -R a+Xr /user-mount/denied-source")
+          machine.succeed("mkdir /user-mount/denied-point")
+          machine.succeed("chown alice:users /user-mount/denied-point")
+          machine.fail("su - alice -c 'mount /user-mount/denied-point'")
+
+      with subtest("shell-vars"):
+          machine.succeed('[ -n "$NIX_PATH" ]')
+
+      with subtest("nix-db"):
+          machine.succeed("nix-store -qR /run/current-system | grep nixos-")
+
+      with subtest("Test sysctl"):
+          machine.wait_for_unit("systemd-sysctl.service")
+          assert "1" == machine.succeed("sysctl -ne vm.swappiness").strip()
+          machine.execute("sysctl vm.swappiness=60")
+          assert "60" == machine.succeed("sysctl -ne vm.swappiness").strip()
+
+      with subtest("Test boot parameters"):
+          assert "vsyscall=emulate" in machine.succeed("cat /proc/cmdline")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/mobilizon.nix b/nixpkgs/nixos/tests/mobilizon.nix
new file mode 100644
index 000000000000..2b070ca9d960
--- /dev/null
+++ b/nixpkgs/nixos/tests/mobilizon.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ lib, ... }:
+  let
+    certs = import ./common/acme/server/snakeoil-certs.nix;
+    mobilizonDomain = certs.domain;
+    port = 41395;
+  in
+
+  {
+    name = "mobilizon";
+    meta.maintainers = with lib.maintainers; [ minijackson erictapen ];
+
+    nodes.server =
+      { ... }:
+      {
+        services.mobilizon = {
+          enable = true;
+          settings = {
+            ":mobilizon" = {
+              ":instance" = {
+                name = "Test Mobilizon";
+                hostname = mobilizonDomain;
+              };
+              "Mobilizon.Web.Endpoint".http.port = port;
+            };
+          };
+        };
+
+        security.pki.certificateFiles = [ certs.ca.cert ];
+
+        services.nginx.virtualHosts."${mobilizonDomain}" = {
+          enableACME = lib.mkForce false;
+          sslCertificate = certs.${mobilizonDomain}.cert;
+          sslCertificateKey = certs.${mobilizonDomain}.key;
+        };
+
+        networking.hosts."::1" = [ mobilizonDomain ];
+      };
+
+    testScript = ''
+      server.wait_for_unit("mobilizon.service")
+      server.wait_for_open_port(${toString port})
+      server.succeed("curl --fail https://${mobilizonDomain}/")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/mod_perl.nix b/nixpkgs/nixos/tests/mod_perl.nix
new file mode 100644
index 000000000000..f29d79ea6206
--- /dev/null
+++ b/nixpkgs/nixos/tests/mod_perl.nix
@@ -0,0 +1,53 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "mod_perl";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ sgo ];
+  };
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    services.httpd = {
+      enable = true;
+      adminAddr = "admin@localhost";
+      virtualHosts."modperl" =
+        let
+          inc = pkgs.writeTextDir "ModPerlTest.pm" ''
+            package ModPerlTest;
+            use strict;
+            use Apache2::RequestRec ();
+            use Apache2::RequestIO ();
+            use Apache2::Const -compile => qw(OK);
+            sub handler {
+              my $r = shift;
+              $r->content_type('text/plain');
+              print "Hello mod_perl!\n";
+              return Apache2::Const::OK;
+            }
+            1;
+          '';
+          startup = pkgs.writeScript "startup.pl" ''
+            use lib "${inc}",
+              split ":","${with pkgs.perl.pkgs; makeFullPerlPath ([ mod_perl2 ])}";
+            1;
+          '';
+        in
+        {
+          extraConfig = ''
+            PerlRequire ${startup}
+          '';
+          locations."/modperl" = {
+            extraConfig = ''
+              SetHandler perl-script
+              PerlResponseHandler ModPerlTest
+            '';
+          };
+        };
+      enablePerl = true;
+    };
+  };
+  testScript = { ... }: ''
+    machine.wait_for_unit("httpd.service")
+    response = machine.succeed("curl -fvvv -s http://127.0.0.1:80/modperl")
+    assert "Hello mod_perl!" in response, "/modperl handler did not respond"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/molly-brown.nix b/nixpkgs/nixos/tests/molly-brown.nix
new file mode 100644
index 000000000000..bfc036e81ba0
--- /dev/null
+++ b/nixpkgs/nixos/tests/molly-brown.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  let testString = "NixOS Gemini test successful";
+  in {
+
+    name = "molly-brown";
+    meta = with pkgs.lib.maintainers; { maintainers = [ ehmry ]; };
+
+    nodes = {
+
+      geminiServer = { config, pkgs, ... }:
+        let
+          inherit (config.networking) hostName;
+          cfg = config.services.molly-brown;
+        in {
+
+          environment.systemPackages = [
+            (pkgs.writeScriptBin "test-gemini" ''
+              #!${pkgs.python3}/bin/python
+
+              import socket
+              import ssl
+              import tempfile
+              import textwrap
+              import urllib.parse
+
+              url = "gemini://geminiServer/init.gmi"
+              parsed_url = urllib.parse.urlparse(url)
+
+              s = socket.create_connection((parsed_url.netloc, 1965))
+              context = ssl.SSLContext()
+              context.check_hostname = False
+              context.verify_mode = ssl.CERT_NONE
+              s = context.wrap_socket(s, server_hostname=parsed_url.netloc)
+              s.sendall((url + "\r\n").encode("UTF-8"))
+              fp = s.makefile("rb")
+              print(fp.readline().strip())
+              print(fp.readline().strip())
+              print(fp.readline().strip())
+            '')
+          ];
+
+          networking.firewall.allowedTCPPorts = [ cfg.settings.Port ];
+
+          services.molly-brown = {
+            enable = true;
+            docBase = "/tmp/docs";
+            certPath = "/tmp/cert.pem";
+            keyPath = "/tmp/key.pem";
+          };
+
+          systemd.services.molly-brown.preStart = ''
+            ${pkgs.openssl}/bin/openssl genrsa -out "/tmp/key.pem"
+            ${pkgs.openssl}/bin/openssl req -new \
+              -subj "/CN=${config.networking.hostName}" \
+              -key "/tmp/key.pem" -out /tmp/request.pem
+            ${pkgs.openssl}/bin/openssl x509 -req -days 3650 \
+              -in /tmp/request.pem -signkey "/tmp/key.pem" -out "/tmp/cert.pem"
+
+            mkdir -p "${cfg.settings.DocBase}"
+            echo "${testString}" > "${cfg.settings.DocBase}/test.gmi"
+          '';
+        };
+    };
+    testScript = ''
+      geminiServer.wait_for_unit("molly-brown")
+      geminiServer.wait_for_open_port(1965)
+      geminiServer.succeed("test-gemini")
+    '';
+
+  })
diff --git a/nixpkgs/nixos/tests/mongodb.nix b/nixpkgs/nixos/tests/mongodb.nix
new file mode 100644
index 000000000000..68be6926865e
--- /dev/null
+++ b/nixpkgs/nixos/tests/mongodb.nix
@@ -0,0 +1,50 @@
+# This test start mongodb, runs a query using mongo shell
+
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    testQuery = pkgs.writeScript "nixtest.js" ''
+      db.greetings.insert({ "greeting": "hello" });
+      print(db.greetings.findOne().greeting);
+    '';
+
+    runMongoDBTest = pkg: ''
+      node.execute("(rm -rf data || true) && mkdir data")
+      node.execute(
+          "${pkg}/bin/mongod --fork --logpath logs --dbpath data"
+      )
+      node.wait_for_open_port(27017)
+
+      assert "hello" in node.succeed(
+          "${pkg}/bin/mongo ${testQuery}"
+      )
+
+      node.execute(
+          "${pkg}/bin/mongod --shutdown --dbpath data"
+      )
+      node.wait_for_closed_port(27017)
+    '';
+
+  in {
+    name = "mongodb";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bluescreen303 offline phile314 ];
+    };
+
+    nodes = {
+      node = {...}: {
+        environment.systemPackages = with pkgs; [
+          mongodb-4_4
+          mongodb-5_0
+        ];
+      };
+    };
+
+    testScript = ''
+      node.start()
+    ''
+      + runMongoDBTest pkgs.mongodb-4_4
+      + runMongoDBTest pkgs.mongodb-5_0
+      + ''
+        node.shutdown()
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/moodle.nix b/nixpkgs/nixos/tests/moodle.nix
new file mode 100644
index 000000000000..8fd011e0cb21
--- /dev/null
+++ b/nixpkgs/nixos/tests/moodle.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "moodle";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  nodes.machine =
+    { ... }:
+    { services.moodle.enable = true;
+      services.moodle.virtualHost.hostName = "localhost";
+      services.moodle.virtualHost.adminAddr = "root@example.com";
+      services.moodle.initialPassword = "correcthorsebatterystaple";
+
+      # Ensure the virtual machine has enough memory to avoid errors like:
+      # Fatal error: Out of memory (allocated 152047616) (tried to allocate 33554440 bytes)
+      virtualisation.memorySize = 2000;
+    };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("phpfpm-moodle.service", timeout=1800)
+    machine.wait_until_succeeds("curl http://localhost/ | grep 'You are not logged in'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/moonraker.nix b/nixpkgs/nixos/tests/moonraker.nix
new file mode 100644
index 000000000000..b0a93a4a608b
--- /dev/null
+++ b/nixpkgs/nixos/tests/moonraker.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "moonraker";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ zhaofengli ];
+  };
+
+  nodes = {
+    printer = { config, pkgs, ... }: {
+      security.polkit.enable = true;
+
+      services.moonraker = {
+        enable = true;
+        allowSystemControl = true;
+
+        settings = {
+          authorization = {
+            trusted_clients = [ "127.0.0.0/8" "::1/128" ];
+          };
+        };
+      };
+
+      services.klipper = {
+        enable = true;
+
+        user = "moonraker";
+        group = "moonraker";
+
+        # No mcu configured so won't even enter `ready` state
+        settings = {};
+      };
+    };
+  };
+
+  testScript = ''
+    printer.start()
+
+    printer.wait_for_unit("klipper.service")
+    printer.wait_for_unit("moonraker.service")
+    printer.wait_until_succeeds("curl http://localhost:7125/printer/info | grep -v 'Not Found' >&2", timeout=30)
+
+    with subtest("Check that we can perform system-level operations"):
+        printer.succeed("curl -X POST http://localhost:7125/machine/services/stop?service=klipper | grep ok >&2")
+        printer.wait_until_succeeds("systemctl --no-pager show klipper.service | grep ActiveState=inactive", timeout=10)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/moosefs.nix b/nixpkgs/nixos/tests/moosefs.nix
new file mode 100644
index 000000000000..0dc08748b828
--- /dev/null
+++ b/nixpkgs/nixos/tests/moosefs.nix
@@ -0,0 +1,89 @@
+import ./make-test-python.nix ({ pkgs, ... } :
+
+let
+  master = { pkgs, ... } : {
+    # data base is stored in memory
+    # server crashes with default memory size
+    virtualisation.memorySize = 1024;
+
+    services.moosefs.master = {
+      enable = true;
+      openFirewall = true;
+      exports = [
+        "* / rw,alldirs,admin,maproot=0:0"
+        "* . rw"
+      ];
+    };
+  };
+
+  chunkserver = { pkgs, ... } : {
+    virtualisation.emptyDiskImages = [ 4096 ];
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb
+    '';
+
+    fileSystems = pkgs.lib.mkVMOverride {
+      "/data" = {
+        device = "/dev/disk/by-label/data";
+        fsType = "ext4";
+      };
+    };
+
+    services.moosefs = {
+      masterHost = "master";
+      chunkserver = {
+        openFirewall = true;
+        enable = true;
+        hdds = [ "~/data" ];
+      };
+    };
+  };
+
+  metalogger = { pkgs, ... } : {
+    services.moosefs = {
+      masterHost = "master";
+      metalogger.enable = true;
+    };
+  };
+
+  client = { pkgs, ... } : {
+    services.moosefs.client.enable = true;
+  };
+
+in {
+  name = "moosefs";
+
+  nodes= {
+    inherit master;
+    inherit metalogger;
+    chunkserver1 = chunkserver;
+    chunkserver2 = chunkserver;
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = ''
+    # prepare master server
+    master.start()
+    master.wait_for_unit("multi-user.target")
+    master.succeed("mfsmaster-init")
+    master.succeed("systemctl restart mfs-master")
+    master.wait_for_unit("mfs-master.service")
+
+    metalogger.wait_for_unit("mfs-metalogger.service")
+
+    for chunkserver in [chunkserver1, chunkserver2]:
+        chunkserver.wait_for_unit("multi-user.target")
+        chunkserver.succeed("chown moosefs:moosefs /data")
+        chunkserver.succeed("systemctl restart mfs-chunkserver")
+        chunkserver.wait_for_unit("mfs-chunkserver.service")
+
+    for client in [client1, client2]:
+        client.wait_for_unit("multi-user.target")
+        client.succeed("mkdir /moosefs")
+        client.succeed("mount -t moosefs master:/ /moosefs")
+
+    client1.succeed("echo test > /moosefs/file")
+    client2.succeed("grep test /moosefs/file")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/morph-browser.nix b/nixpkgs/nixos/tests/morph-browser.nix
new file mode 100644
index 000000000000..859e6bb47646
--- /dev/null
+++ b/nixpkgs/nixos/tests/morph-browser.nix
@@ -0,0 +1,53 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "morph-browser-standalone";
+  meta.maintainers = lib.teams.lomiri.members;
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+
+    environment = {
+      systemPackages = with pkgs.lomiri; [
+        suru-icon-theme
+        morph-browser
+      ];
+      variables = {
+        UITK_ICON_THEME = "suru";
+      };
+    };
+
+    i18n.supportedLocales = [ "all" ];
+
+    fonts.packages = with pkgs; [
+      # Intended font & helps with OCR
+      ubuntu_font_family
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+
+      with subtest("morph browser launches"):
+          machine.execute("morph-browser >&2 &")
+          machine.wait_for_text(r"Web Browser|New|sites|Bookmarks")
+          machine.screenshot("morph_open")
+
+      with subtest("morph browser displays HTML"):
+          machine.send_chars("file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html\n")
+          machine.wait_for_text("Valgrind Documentation")
+          machine.screenshot("morph_htmlcontent")
+
+      machine.succeed("pkill -f morph-browser")
+
+      with subtest("morph browser localisation works"):
+          machine.execute("env LANG=de_DE.UTF-8 morph-browser >&2 &")
+          machine.wait_for_text(r"Web-Browser|Neuer|Seiten|Lesezeichen")
+          machine.screenshot("morph_localised")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/morty.nix b/nixpkgs/nixos/tests/morty.nix
new file mode 100644
index 000000000000..9909596820d3
--- /dev/null
+++ b/nixpkgs/nixos/tests/morty.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "morty";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ leenaars ];
+  };
+
+  nodes =
+    { mortyProxyWithKey =
+
+      { ... }:
+      { services.morty = {
+        enable = true;
+        key = "78a9cd0cfee20c672f78427efb2a2a96036027f0";
+        port = 3001;
+        };
+      };
+
+    };
+
+  testScript =
+    { ... }:
+    ''
+      mortyProxyWithKey.wait_for_unit("default.target")
+      mortyProxyWithKey.wait_for_open_port(3001)
+      mortyProxyWithKey.succeed("curl -fL 127.0.0.1:3001 | grep MortyProxy")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/mosquitto.nix b/nixpkgs/nixos/tests/mosquitto.nix
new file mode 100644
index 000000000000..c0980b23e78f
--- /dev/null
+++ b/nixpkgs/nixos/tests/mosquitto.nix
@@ -0,0 +1,213 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  port = 1888;
+  tlsPort = 1889;
+  anonPort = 1890;
+  password = "VERY_secret";
+  hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw==";
+  topic = "test/foo";
+
+  snakeOil = pkgs.runCommand "snakeoil-certs" {
+    buildInputs = [ pkgs.gnutls.bin ];
+    caTemplate = pkgs.writeText "snakeoil-ca.template" ''
+      cn = server
+      expiration_days = -1
+      cert_signing_key
+      ca
+    '';
+    certTemplate = pkgs.writeText "snakeoil-cert.template" ''
+      cn = server
+      expiration_days = -1
+      tls_www_server
+      encryption_key
+      signing_key
+    '';
+    userCertTemplate = pkgs.writeText "snakeoil-user-cert.template" ''
+      organization = snakeoil
+      cn = client1
+      expiration_days = -1
+      tls_www_client
+      encryption_key
+      signing_key
+    '';
+  } ''
+    mkdir "$out"
+
+    certtool -p --bits 2048 --outfile "$out/ca.key"
+    certtool -s --template "$caTemplate" --load-privkey "$out/ca.key" \
+                --outfile "$out/ca.crt"
+    certtool -p --bits 2048 --outfile "$out/server.key"
+    certtool -c --template "$certTemplate" \
+                --load-ca-privkey "$out/ca.key" \
+                --load-ca-certificate "$out/ca.crt" \
+                --load-privkey "$out/server.key" \
+                --outfile "$out/server.crt"
+
+    certtool -p --bits 2048 --outfile "$out/client1.key"
+    certtool -c --template "$userCertTemplate" \
+                --load-privkey "$out/client1.key" \
+                --load-ca-privkey "$out/ca.key" \
+                --load-ca-certificate "$out/ca.crt" \
+                --outfile "$out/client1.crt"
+  '';
+
+in {
+  name = "mosquitto";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [ pennae peterhoeg ];
+  };
+
+  nodes = let
+    client = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ mosquitto ];
+    };
+  in {
+    server = { pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ port tlsPort anonPort ];
+      networking.useNetworkd = true;
+      services.mosquitto = {
+        enable = true;
+        settings = {
+          sys_interval = 1;
+        };
+        listeners = [
+          {
+            inherit port;
+            users = {
+              password_store = {
+                inherit password;
+              };
+              password_file = {
+                passwordFile = pkgs.writeText "mqtt-password" password;
+              };
+              hashed_store = {
+                inherit hashedPassword;
+              };
+              hashed_file = {
+                hashedPasswordFile = pkgs.writeText "mqtt-hashed-password" hashedPassword;
+              };
+
+              reader = {
+                inherit password;
+                acl = [
+                  "read ${topic}"
+                  "read $SYS/#" # so we always have something to read
+                ];
+              };
+              writer = {
+                inherit password;
+                acl = [ "write ${topic}" ];
+              };
+            };
+          }
+          {
+            port = tlsPort;
+            users.client1 = {
+              acl = [ "read $SYS/#" ];
+            };
+            settings = {
+              cafile = "${snakeOil}/ca.crt";
+              certfile = "${snakeOil}/server.crt";
+              keyfile = "${snakeOil}/server.key";
+              require_certificate = true;
+              use_identity_as_username = true;
+            };
+          }
+          {
+            port = anonPort;
+            omitPasswordAuth = true;
+            settings.allow_anonymous = true;
+            acl = [ "pattern read #" ];
+            users = {
+              anonWriter = {
+                password = "<ignored>" + password;
+                acl = [ "write ${topic}" ];
+              };
+            };
+          }
+        ];
+      };
+    };
+
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = ''
+    def mosquitto_cmd(binary, user, topic, port):
+        return (
+            "mosquitto_{} "
+            "-V mqttv311 "
+            "-h server "
+            "-p {} "
+            "-u {} "
+            "-P '${password}' "
+            "-t '{}'"
+        ).format(binary, port, user, topic)
+
+
+    def publish(args, user, topic="${topic}", port=${toString port}):
+        return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args)
+
+    def subscribe(args, user, topic="${topic}", port=${toString port}):
+        return "{} -W 5 -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args)
+
+    def parallel(*fns):
+        from threading import Thread
+        threads = [ Thread(target=fn) for fn in fns ]
+        for t in threads: t.start()
+        for t in threads: t.join()
+
+    def wait_uuid(uuid):
+        server.wait_for_console_text(uuid)
+        return None
+
+
+    start_all()
+    server.wait_for_unit("mosquitto.service")
+
+    with subtest("check passwords"):
+        client1.succeed(publish("-m test", "password_store"))
+        client1.succeed(publish("-m test", "password_file"))
+        client1.succeed(publish("-m test", "hashed_store"))
+        client1.succeed(publish("-m test", "hashed_file"))
+
+    with subtest("check acl"):
+        client1.succeed(subscribe("", "reader", topic="$SYS/#"))
+        client1.fail(subscribe("", "writer", topic="$SYS/#"))
+
+        parallel(
+            lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")),
+            lambda: [
+                wait_uuid("3688cdd7-aa07-42a4-be22-cb9352917e40"),
+                client2.succeed(publish("-m test", "writer"))
+            ])
+
+        parallel(
+            lambda: client1.fail(subscribe("-i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")),
+            lambda: [
+                wait_uuid("24ff16a2-ae33-4a51-9098-1b417153c712"),
+                client2.succeed(publish("-m test", "reader"))
+            ])
+
+    with subtest("check tls"):
+        client1.succeed(
+            subscribe(
+                "--cafile ${snakeOil}/ca.crt "
+                "--cert ${snakeOil}/client1.crt "
+                "--key ${snakeOil}/client1.key",
+                topic="$SYS/#",
+                port=${toString tlsPort},
+                user="no_such_user"))
+
+    with subtest("check omitPasswordAuth"):
+        parallel(
+            lambda: client1.succeed(subscribe("-i fd56032c-d9cb-4813-a3b4-6be0e04c8fc3",
+                "anonReader", port=${toString anonPort})),
+            lambda: [
+                wait_uuid("fd56032c-d9cb-4813-a3b4-6be0e04c8fc3"),
+                client2.succeed(publish("-m test", "anonWriter", port=${toString anonPort}))
+            ])
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mpd.nix b/nixpkgs/nixos/tests/mpd.nix
new file mode 100644
index 000000000000..52d9c7fd33a1
--- /dev/null
+++ b/nixpkgs/nixos/tests/mpd.nix
@@ -0,0 +1,134 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    track = pkgs.fetchurl {
+      # Sourced from http://freemusicarchive.org/music/Blue_Wave_Theory/Surf_Music_Month_Challenge/Skyhawk_Beach_fade_in
+      # License: http://creativecommons.org/licenses/by-sa/4.0/
+
+      name = "Blue_Wave_Theory-Skyhawk_Beach.mp3";
+      url = "https://freemusicarchive.org/file/music/ccCommunity/Blue_Wave_Theory/Surf_Music_Month_Challenge/Blue_Wave_Theory_-_04_-_Skyhawk_Beach.mp3";
+      sha256 = "0xw417bxkx4gqqy139bb21yldi37xx8xjfxrwaqa0gyw19dl6mgp";
+    };
+
+    defaultCfg = rec {
+      user = "mpd";
+      group = "mpd";
+      dataDir = "/var/lib/mpd";
+      musicDirectory = "${dataDir}/music";
+    };
+
+    defaultMpdCfg = with defaultCfg; {
+      inherit dataDir musicDirectory user group;
+      enable = true;
+    };
+
+    musicService = { user, group, musicDirectory }: {
+      description = "Sets up the music file(s) for MPD to use.";
+      requires = [ "mpd.service" ];
+      after = [ "mpd.service" ];
+      wantedBy = [ "default.target" ];
+      script = ''
+        cp ${track} ${musicDirectory}
+      '';
+      serviceConfig = {
+        User = user;
+        Group = group;
+      };
+    };
+
+    mkServer = { mpd, musicService, }:
+      { boot.kernelModules = [ "snd-dummy" ];
+        sound.enable = true;
+        services.mpd = mpd;
+        systemd.services.musicService = musicService;
+      };
+  in {
+    name = "mpd";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ emmanuelrosa ];
+    };
+
+  nodes =
+    { client =
+      { ... }: { };
+
+      serverALSA =
+        { ... }: lib.mkMerge [
+          (mkServer {
+            mpd = defaultMpdCfg // {
+              network.listenAddress = "any";
+              extraConfig = ''
+                audio_output {
+                  type "alsa"
+                  name "ALSA"
+                  mixer_type "null"
+                }
+              '';
+            };
+            musicService = with defaultMpdCfg; musicService { inherit user group musicDirectory; };
+          })
+          { networking.firewall.allowedTCPPorts = [ 6600 ]; }
+        ];
+
+      serverPulseAudio =
+        { ... }: lib.mkMerge [
+          (mkServer {
+            mpd = defaultMpdCfg // {
+              extraConfig = ''
+                audio_output {
+                  type "pulse"
+                  name "The Pulse"
+                }
+              '';
+            };
+
+            musicService = with defaultCfg; musicService { inherit user group musicDirectory; };
+          })
+          {
+            hardware.pulseaudio = {
+              enable = true;
+              systemWide = true;
+              tcp.enable = true;
+              tcp.anonymousClients.allowAll = true;
+            };
+            systemd.services.mpd.environment.PULSE_SERVER = "localhost";
+          }
+        ];
+    };
+
+  testScript = ''
+    mpc = "${pkgs.mpc-cli}/bin/mpc --wait"
+
+    # Connects to the given server and attempts to play a tune.
+    def play_some_music(server):
+        server.wait_for_unit("mpd.service")
+        server.succeed(f"{mpc} update")
+        _, tracks = server.execute(f"{mpc} ls")
+
+        for track in tracks.splitlines():
+            server.succeed(f"{mpc} add {track}")
+
+        _, added_tracks = server.execute(f"{mpc} playlist")
+
+        # Check we succeeded adding audio tracks to the playlist
+        assert len(added_tracks.splitlines()) > 0
+
+        server.succeed(f"{mpc} play")
+
+        _, output = server.execute(f"{mpc} status")
+        # Assure audio track is playing
+        assert "playing" in output
+
+        server.succeed(f"{mpc} stop")
+
+
+    play_some_music(serverALSA)
+    play_some_music(serverPulseAudio)
+
+    client.wait_for_unit("multi-user.target")
+    client.succeed(f"{mpc} -h serverALSA status")
+
+    # The PulseAudio-based server is configured not to accept external client connections
+    # to perform the following test:
+    client.fail(f"{mpc} -h serverPulseAudio status")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mpich-example.c b/nixpkgs/nixos/tests/mpich-example.c
new file mode 100644
index 000000000000..c48e3c45b72e
--- /dev/null
+++ b/nixpkgs/nixos/tests/mpich-example.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <mpi.h>
+
+int
+main (int argc, char *argv[])
+{
+  int rank, size, length;
+  char name[BUFSIZ];
+
+  MPI_Init (&argc, &argv);
+  MPI_Comm_rank (MPI_COMM_WORLD, &rank);
+  MPI_Comm_size (MPI_COMM_WORLD, &size);
+  MPI_Get_processor_name (name, &length);
+
+  printf ("%s: hello world from process %d of %d\n", name, rank, size);
+
+  MPI_Finalize ();
+
+  return EXIT_SUCCESS;
+}
diff --git a/nixpkgs/nixos/tests/mpv.nix b/nixpkgs/nixos/tests/mpv.nix
new file mode 100644
index 000000000000..32a81cbe2495
--- /dev/null
+++ b/nixpkgs/nixos/tests/mpv.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+let
+  port = toString 4321;
+in
+{
+  name = "mpv";
+  meta.maintainers = with lib.maintainers; [ zopieux ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      environment.systemPackages = [
+        pkgs.curl
+        (pkgs.wrapMpv pkgs.mpv-unwrapped {
+          scripts = [ pkgs.mpvScripts.simple-mpv-webui ];
+        })
+      ];
+    };
+
+  testScript = ''
+    machine.execute("set -m; mpv --script-opts=webui-port=${port} --idle=yes >&2 &")
+    machine.wait_for_open_port(${port})
+    assert "<title>simple-mpv-webui" in machine.succeed("curl -s localhost:${port}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mtp.nix b/nixpkgs/nixos/tests/mtp.nix
new file mode 100644
index 000000000000..8f0835d75d3f
--- /dev/null
+++ b/nixpkgs/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/nixpkgs/nixos/tests/multipass.nix b/nixpkgs/nixos/tests/multipass.nix
new file mode 100644
index 000000000000..0980e9195f5a
--- /dev/null
+++ b/nixpkgs/nixos/tests/multipass.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  multipass-image = import ../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+    };
+  };
+
+in
+{
+  name = "multipass";
+
+  meta.maintainers = [ lib.maintainers.jnsgruk ];
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      cores = 1;
+      memorySize = 1024;
+      diskSize = 4096;
+
+      multipass.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("multipass.service")
+    machine.wait_for_file("/var/lib/multipass/data/multipassd/network/multipass_subnet")
+
+    # Wait for Multipass to settle
+    machine.sleep(1)
+
+    machine.succeed("multipass list")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mumble.nix b/nixpkgs/nixos/tests/mumble.nix
new file mode 100644
index 000000000000..8eee454721a1
--- /dev/null
+++ b/nixpkgs/nixos/tests/mumble.nix
@@ -0,0 +1,89 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  client = { pkgs, ... }: {
+    imports = [ ./common/x11.nix ];
+    environment.systemPackages = [ pkgs.mumble ];
+  };
+
+  # outside of tests, this file should obviously not come from the nix store
+  envFile = pkgs.writeText "nixos-test-mumble-murmurd.env" ''
+    MURMURD_PASSWORD=testpassword
+  '';
+
+in
+{
+  name = "mumble";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ thoughtpolice eelco ];
+  };
+
+  nodes = {
+    server = { config, ... }: {
+      security.apparmor.enable = true;
+      services.murmur.enable = true;
+      services.murmur.registerName = "NixOS tests";
+      services.murmur.password = "$MURMURD_PASSWORD";
+      services.murmur.environmentFile = envFile;
+      networking.firewall.allowedTCPPorts = [ config.services.murmur.port ];
+    };
+
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("murmur.service")
+    client1.wait_for_x()
+    client2.wait_for_x()
+
+    client1.execute("mumble mumble://client1:testpassword\@server/test >&2 &")
+    client2.execute("mumble mumble://client2:testpassword\@server/test >&2 &")
+
+    # cancel client audio configuration
+    client1.wait_for_window(r"Audio Tuning Wizard")
+    client2.wait_for_window(r"Audio Tuning Wizard")
+    server.sleep(5)  # wait because mumble is slow to register event handlers
+    client1.send_key("esc")
+    client2.send_key("esc")
+
+    # cancel client cert configuration
+    client1.wait_for_window(r"Certificate Management")
+    client2.wait_for_window(r"Certificate Management")
+    server.sleep(5)  # wait because mumble is slow to register event handlers
+    client1.send_key("esc")
+    client2.send_key("esc")
+
+    # accept server certificate
+    client1.wait_for_window(r"^Mumble$")
+    client2.wait_for_window(r"^Mumble$")
+    server.sleep(5)  # wait because mumble is slow to register event handlers
+    client1.send_chars("y")
+    client2.send_chars("y")
+    server.sleep(5)  # wait because mumble is slow to register event handlers
+
+    # sometimes the wrong of the 2 windows is focused, we switch focus and try pressing "y" again
+    client1.send_key("alt-tab")
+    client2.send_key("alt-tab")
+    server.sleep(5)  # wait because mumble is slow to register event handlers
+    client1.send_chars("y")
+    client2.send_chars("y")
+
+    # Find clients in logs
+    server.wait_until_succeeds(
+        "journalctl -eu murmur -o cat | grep -q 'client1.\+Authenticated'"
+    )
+    server.wait_until_succeeds(
+        "journalctl -eu murmur -o cat | grep -q 'client2.\+Authenticated'"
+    )
+
+    server.sleep(5)  # wait to get screenshot
+    client1.screenshot("screen1")
+    client2.screenshot("screen2")
+
+    # check if apparmor denied anything
+    server.fail('journalctl -b --no-pager --grep "^audit: .*apparmor=\\"DENIED\\""')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/munin.nix b/nixpkgs/nixos/tests/munin.nix
new file mode 100644
index 000000000000..e371b2dffa6b
--- /dev/null
+++ b/nixpkgs/nixos/tests/munin.nix
@@ -0,0 +1,46 @@
+# This test runs basic munin setup with node and cron job running on the same
+# machine.
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "munin";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ domenkozar eelco ];
+  };
+
+  nodes = {
+    one =
+      { config, ... }:
+        {
+          services = {
+            munin-node = {
+              enable = true;
+              # disable a failing plugin to prevent irrelevant error message, see #23049
+              disabledPlugins = [ "apc_nis" ];
+            };
+            munin-cron = {
+             enable = true;
+             hosts = ''
+               [${config.networking.hostName}]
+               address localhost
+             '';
+            };
+          };
+
+          # increase the systemd timer interval so it fires more often
+          systemd.timers.munin-cron.timerConfig.OnCalendar = pkgs.lib.mkForce "*:*:0/10";
+        };
+    };
+
+  testScript = ''
+    start_all()
+
+    with subtest("ensure munin-node starts and listens on 4949"):
+        one.wait_for_unit("munin-node.service")
+        one.wait_for_open_port(4949)
+
+    with subtest("ensure munin-cron output is correct"):
+        one.wait_for_file("/var/lib/munin/one/one-uptime-uptime-g.rrd")
+        one.wait_for_file("/var/www/munin/one/index.html")
+        one.wait_for_file("/var/www/munin/one/one/diskstat_iops_vda-day.png", timeout=60)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/musescore.nix b/nixpkgs/nixos/tests/musescore.nix
new file mode 100644
index 000000000000..0720631ed284
--- /dev/null
+++ b/nixpkgs/nixos/tests/musescore.nix
@@ -0,0 +1,103 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  # Make sure we don't have to go through the startup tutorial
+  customMuseScoreConfig = pkgs.writeText "MuseScore4.ini" ''
+    [application]
+    hasCompletedFirstLaunchSetup=true
+
+    [project]
+    preferredScoreCreationMode=1
+    '';
+in
+{
+  name = "musescore";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ turion ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = with pkgs; [
+      musescore
+      pdfgrep
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript = { ... }: ''
+    start_all()
+    machine.wait_for_x()
+
+    # Inject custom settings
+    machine.succeed("mkdir -p /root/.config/MuseScore/")
+    machine.succeed(
+        "cp ${customMuseScoreConfig} /root/.config/MuseScore/MuseScore4.ini"
+    )
+
+    # Start MuseScore window
+    machine.execute("DISPLAY=:0.0 mscore >&2 &")
+
+    # Wait until MuseScore has launched
+    machine.wait_for_window("MuseScore 4")
+
+    # Wait until the window has completely initialised
+    machine.wait_for_text("MuseScore 4")
+
+    machine.screenshot("MuseScore0")
+
+    # Create a new score
+    machine.send_key("ctrl-n")
+
+    # Wait until the creation wizard appears
+    machine.wait_for_window("New score")
+
+    machine.screenshot("MuseScore1")
+
+    machine.send_key("tab")
+    machine.send_key("tab")
+    machine.send_key("ret")
+
+    machine.sleep(2)
+
+    machine.send_key("tab")
+    # Type the beginning of https://de.wikipedia.org/wiki/Alle_meine_Entchen
+    machine.send_chars("cdef6gg5aaaa7g")
+    machine.sleep(1)
+
+    machine.screenshot("MuseScore2")
+
+    # Go to the export dialogue and create a PDF
+    machine.send_key("alt-f")
+    machine.sleep(1)
+    machine.send_key("e")
+
+    # Wait until the export dialogue appears.
+    machine.wait_for_text("Export")
+
+    machine.screenshot("MuseScore3")
+
+    machine.send_key("shift-tab")
+    machine.sleep(1)
+    machine.send_key("ret")
+    machine.sleep(1)
+    machine.send_key("ret")
+
+    machine.screenshot("MuseScore4")
+
+    # Wait until PDF is exported
+    machine.wait_for_file('"/root/Documents/MuseScore4/Scores/Untitled score.pdf"')
+
+    # Check that it contains the title of the score
+    machine.succeed('pdfgrep "Untitled score" "/root/Documents/MuseScore4/Scores/Untitled score.pdf"')
+
+    machine.screenshot("MuseScore5")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mutable-users.nix b/nixpkgs/nixos/tests/mutable-users.nix
new file mode 100644
index 000000000000..ebe32e6487ef
--- /dev/null
+++ b/nixpkgs/nixos/tests/mutable-users.nix
@@ -0,0 +1,73 @@
+# Mutable users tests.
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "mutable-users";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ gleber ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      users.mutableUsers = false;
+    };
+    mutable = { ... }: {
+      users.mutableUsers = true;
+      users.users.dry-test.isNormalUser = true;
+    };
+  };
+
+  testScript = {nodes, ...}: let
+    immutableSystem = nodes.machine.config.system.build.toplevel;
+    mutableSystem = nodes.mutable.config.system.build.toplevel;
+  in ''
+    machine.start()
+    machine.wait_for_unit("default.target")
+
+    # Machine starts in immutable mode. Add a user and test if reactivating
+    # configuration removes the user.
+    with subtest("Machine in immutable mode"):
+        assert "foobar" not in machine.succeed("cat /etc/passwd")
+        machine.succeed("sudo useradd foobar")
+        assert "foobar" in machine.succeed("cat /etc/passwd")
+        machine.succeed(
+            "${immutableSystem}/bin/switch-to-configuration test"
+        )
+        assert "foobar" not in machine.succeed("cat /etc/passwd")
+
+    # In immutable mode passwd is not wrapped, while in mutable mode it is
+    # wrapped.
+    with subtest("Password is wrapped in mutable mode"):
+        assert "/run/current-system/" in machine.succeed("which passwd")
+        machine.succeed(
+            "${mutableSystem}/bin/switch-to-configuration test"
+        )
+        assert "/run/wrappers/" in machine.succeed("which passwd")
+
+    with subtest("dry-activation does not change files"):
+        machine.succeed('test -e /home/dry-test')  # home was created
+        machine.succeed('rm -rf /home/dry-test')
+
+        files_to_check = ['/etc/group',
+                          '/etc/passwd',
+                          '/etc/shadow',
+                          '/etc/subuid',
+                          '/etc/subgid',
+                          '/var/lib/nixos/uid-map',
+                          '/var/lib/nixos/gid-map',
+                          '/var/lib/nixos/declarative-groups',
+                          '/var/lib/nixos/declarative-users'
+                         ]
+        expected_hashes = {}
+        expected_stats = {}
+        for file in files_to_check:
+            expected_hashes[file] = machine.succeed(f"sha256sum {file}")
+            expected_stats[file] = machine.succeed(f"stat {file}")
+
+        machine.succeed("/run/current-system/bin/switch-to-configuration dry-activate")
+
+        machine.fail('test -e /home/dry-test')  # home was not recreated
+        for file in files_to_check:
+            assert machine.succeed(f"sha256sum {file}") == expected_hashes[file]
+            assert machine.succeed(f"stat {file}") == expected_stats[file]
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mxisd.nix b/nixpkgs/nixos/tests/mxisd.nix
new file mode 100644
index 000000000000..354612a8a53d
--- /dev/null
+++ b/nixpkgs/nixos/tests/mxisd.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, ... } : {
+
+  name = "mxisd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mguentner ];
+  };
+
+  nodes = {
+    server = args : {
+      services.mxisd.enable = true;
+      services.mxisd.matrix.domain = "example.org";
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("mxisd.service")
+    server.wait_for_open_port(8090)
+    server.succeed("curl -Ssf 'http://127.0.0.1:8090/_matrix/identity/api/v1'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mympd.nix b/nixpkgs/nixos/tests/mympd.nix
new file mode 100644
index 000000000000..ac6a896966e6
--- /dev/null
+++ b/nixpkgs/nixos/tests/mympd.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({pkgs, lib, ... }: {
+  name = "mympd";
+
+  nodes.mympd = {
+    services.mympd = {
+      enable = true;
+      settings = {
+        http_port = 8081;
+      };
+    };
+
+    services.mpd.enable = true;
+  };
+
+  testScript = ''
+    start_all();
+    machine.wait_for_unit("mympd.service");
+
+    # Ensure that mympd can connect to mpd
+    machine.wait_until_succeeds(
+      "journalctl -eu mympd -o cat | grep 'Connected to MPD'"
+    )
+
+    # Ensure that the web server is working
+    machine.succeed("curl http://localhost:8081 --compressed | grep -o myMPD")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/mysql/common.nix b/nixpkgs/nixos/tests/mysql/common.nix
new file mode 100644
index 000000000000..1cf52347f4c7
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql/common.nix
@@ -0,0 +1,10 @@
+{ lib, pkgs }: {
+  mariadbPackages = lib.filterAttrs (n: _: lib.hasPrefix "mariadb" n) (import ../../../pkgs/servers/sql/mariadb pkgs);
+  mysqlPackages = {
+    inherit (pkgs) mysql80;
+  };
+  perconaPackages = {
+    inherit (pkgs) percona-server_8_0;
+  };
+  mkTestName = pkg: "mariadb_${builtins.replaceStrings ["."] [""] (lib.versions.majorMinor pkg.version)}";
+}
diff --git a/nixpkgs/nixos/tests/mysql/mariadb-galera.nix b/nixpkgs/nixos/tests/mysql/mariadb-galera.nix
new file mode 100644
index 000000000000..7455abbce5fb
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql/mariadb-galera.nix
@@ -0,0 +1,250 @@
+{
+  system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; },
+  lib ? pkgs.lib
+}:
+
+let
+  inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages;
+
+  makeTest = import ./../make-test-python.nix;
+
+  # Common user configuration
+  makeGaleraTest = {
+    mariadbPackage,
+    name ? mkTestName mariadbPackage,
+    galeraPackage ? pkgs.mariadb-galera
+  }: makeTest {
+    name = "${name}-galera-mariabackup";
+    meta = {
+      maintainers = with lib.maintainers; [ izorkin ] ++ lib.teams.helsinki-systems.members;
+    };
+
+    # The test creates a Galera cluster with 3 nodes and is checking if mariabackup-based SST works. The cluster is tested by creating a DB and an empty table on one node,
+    # and checking the table's presence on the other node.
+    nodes = let
+      mkGaleraNode = {
+        id,
+        method
+      }: let
+        address = "192.168.1.${toString id}";
+        isFirstClusterNode = id == 1 || id == 4;
+      in {
+        users = {
+          users.testuser = {
+            isSystemUser = true;
+            group = "testusers";
+          };
+          groups.testusers = { };
+        };
+
+        networking = {
+          interfaces.eth1 = {
+            ipv4.addresses = [
+              { inherit address; prefixLength = 24; }
+            ];
+          };
+          extraHosts = lib.concatMapStringsSep "\n" (i: "192.168.1.${toString i} galera_0${toString i}") (lib.range 1 6);
+          firewall.allowedTCPPorts = [ 3306 4444 4567 4568 ];
+          firewall.allowedUDPPorts = [ 4567 ];
+        };
+        systemd.services.mysql = with pkgs; {
+          path = with pkgs; [
+            bash
+            gawk
+            gnutar
+            gzip
+            inetutils
+            iproute2
+            netcat
+            procps
+            pv
+            rsync
+            socat
+            stunnel
+            which
+          ];
+        };
+        services.mysql = {
+          enable = true;
+          package = mariadbPackage;
+          ensureDatabases = lib.mkIf isFirstClusterNode [ "testdb" ];
+          ensureUsers = lib.mkIf isFirstClusterNode [{
+            name = "testuser";
+            ensurePermissions = {
+              "testdb.*" = "ALL PRIVILEGES";
+            };
+          }];
+          initialScript = lib.mkIf isFirstClusterNode (pkgs.writeText "mariadb-init.sql" ''
+            GRANT ALL PRIVILEGES ON *.* TO 'check_repl'@'localhost' IDENTIFIED BY 'check_pass' WITH GRANT OPTION;
+            FLUSH PRIVILEGES;
+          '');
+          settings = {
+            mysqld = {
+              bind_address = "0.0.0.0";
+            };
+            galera = {
+              wsrep_on = "ON";
+              wsrep_debug = "NONE";
+              wsrep_retry_autocommit = "3";
+              wsrep_provider = "${galeraPackage}/lib/galera/libgalera_smm.so";
+              wsrep_cluster_address = "gcomm://"
+                + lib.optionalString (id == 2 || id == 3) "galera_01,galera_02,galera_03"
+                + lib.optionalString (id == 5 || id == 6) "galera_04,galera_05,galera_06";
+              wsrep_cluster_name = "galera";
+              wsrep_node_address = address;
+              wsrep_node_name = "galera_0${toString id}";
+              wsrep_sst_method = method;
+              wsrep_sst_auth = "check_repl:check_pass";
+              binlog_format = "ROW";
+              enforce_storage_engine = "InnoDB";
+              innodb_autoinc_lock_mode = "2";
+            };
+          };
+        };
+      };
+    in {
+      galera_01 = mkGaleraNode {
+        id = 1;
+        method = "mariabackup";
+      };
+
+      galera_02 = mkGaleraNode {
+        id = 2;
+        method = "mariabackup";
+      };
+
+      galera_03 = mkGaleraNode {
+        id = 3;
+        method = "mariabackup";
+      };
+
+      galera_04 = mkGaleraNode {
+        id = 4;
+        method = "rsync";
+      };
+
+      galera_05 = mkGaleraNode {
+        id = 5;
+        method = "rsync";
+      };
+
+      galera_06 = mkGaleraNode {
+        id = 6;
+        method = "rsync";
+      };
+
+    };
+
+    testScript = ''
+      galera_01.start()
+      galera_01.wait_for_unit("mysql")
+      galera_01.wait_for_open_port(3306)
+      galera_01.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; create table db1 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+      )
+      galera_01.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db1 values (37);'"
+      )
+      galera_02.start()
+      galera_02.wait_for_unit("mysql")
+      galera_02.wait_for_open_port(3306)
+      galera_03.start()
+      galera_03.wait_for_unit("mysql")
+      galera_03.wait_for_open_port(3306)
+      galera_02.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 37"
+      )
+      galera_02.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+      )
+      galera_02.succeed("systemctl stop mysql")
+      galera_01.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db2 values (38);'"
+      )
+      galera_03.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+      )
+      galera_01.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db3 values (39);'"
+      )
+      galera_02.succeed("systemctl start mysql")
+      galera_02.wait_for_open_port(3306)
+      galera_02.succeed(
+          "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_cluster_size.*3'"
+      )
+      galera_03.succeed(
+          "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'"
+      )
+      galera_01.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db3;' -N | grep 39"
+      )
+      galera_02.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db2;' -N | grep 38"
+      )
+      galera_03.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 37"
+      )
+      galera_01.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db3;'")
+      galera_02.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db2;'")
+      galera_03.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db1;'")
+      galera_01.crash()
+      galera_02.crash()
+      galera_03.crash()
+
+      galera_04.start()
+      galera_04.wait_for_unit("mysql")
+      galera_04.wait_for_open_port(3306)
+      galera_04.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; create table db1 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+      )
+      galera_04.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db1 values (41);'"
+      )
+      galera_05.start()
+      galera_05.wait_for_unit("mysql")
+      galera_05.wait_for_open_port(3306)
+      galera_06.start()
+      galera_06.wait_for_unit("mysql")
+      galera_06.wait_for_open_port(3306)
+      galera_05.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 41"
+      )
+      galera_05.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; create table db2 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+      )
+      galera_05.succeed("systemctl stop mysql")
+      galera_04.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db2 values (42);'"
+      )
+      galera_06.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; create table db3 (test_id INT, PRIMARY KEY (test_id)) ENGINE = InnoDB;'"
+      )
+      galera_04.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; insert into db3 values (43);'"
+      )
+      galera_05.succeed("systemctl start mysql")
+      galera_05.wait_for_open_port(3306)
+      galera_05.succeed(
+          "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_cluster_size.*3'"
+      )
+      galera_06.succeed(
+          "sudo -u testuser mysql -u testuser -e 'show status' -N | grep 'wsrep_local_state_comment.*Synced'"
+      )
+      galera_04.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db3;' -N | grep 43"
+      )
+      galera_05.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db2;' -N | grep 42"
+      )
+      galera_06.succeed(
+          "sudo -u testuser mysql -u testuser -e 'use testdb; select test_id from db1;' -N | grep 41"
+      )
+      galera_04.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db3;'")
+      galera_05.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db2;'")
+      galera_06.succeed("sudo -u testuser mysql -u testuser -e 'use testdb; drop table db1;'")
+    '';
+  };
+in
+  lib.mapAttrs (_: mariadbPackage: makeGaleraTest { inherit mariadbPackage; }) mariadbPackages
diff --git a/nixpkgs/nixos/tests/mysql/mysql-autobackup.nix b/nixpkgs/nixos/tests/mysql/mysql-autobackup.nix
new file mode 100644
index 000000000000..b49466db0a9c
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql/mysql-autobackup.nix
@@ -0,0 +1,53 @@
+{
+  system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; },
+  lib ? pkgs.lib
+}:
+
+let
+  inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages;
+
+  makeTest = import ./../make-test-python.nix;
+
+  makeAutobackupTest = {
+    package,
+    name ? mkTestName package,
+  }: makeTest {
+    name = "${name}-automysqlbackup";
+    meta.maintainers = [ lib.maintainers.aanderse ];
+
+    nodes.machine = {
+      services.mysql = {
+        inherit package;
+        enable = true;
+        initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+      };
+
+      services.automysqlbackup.enable = true;
+    };
+
+    testScript = ''
+      start_all()
+
+      # Need to have mysql started so that it can be populated with data.
+      machine.wait_for_unit("mysql.service")
+
+      with subtest("Wait for testdb to be fully populated (5 rows)."):
+          machine.wait_until_succeeds(
+              "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
+          )
+
+      with subtest("Do a backup and wait for it to start"):
+          machine.start_job("automysqlbackup.service")
+          machine.wait_for_job("automysqlbackup.service")
+
+      with subtest("wait for backup file and check that data appears in backup"):
+          machine.wait_for_file("/var/backup/mysql/daily/testdb")
+          machine.succeed(
+              "${pkgs.gzip}/bin/zcat /var/backup/mysql/daily/testdb/daily_testdb_*.sql.gz | grep hello"
+          )
+      '';
+  };
+in
+  lib.mapAttrs (_: package: makeAutobackupTest { inherit package; }) mariadbPackages
diff --git a/nixpkgs/nixos/tests/mysql/mysql-backup.nix b/nixpkgs/nixos/tests/mysql/mysql-backup.nix
new file mode 100644
index 000000000000..451f5c04ce46
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql/mysql-backup.nix
@@ -0,0 +1,68 @@
+{
+  system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; },
+  lib ? pkgs.lib
+}:
+
+let
+  inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages;
+
+  makeTest = import ./../make-test-python.nix;
+
+  makeBackupTest = {
+    package,
+    name ? mkTestName package
+  }: makeTest {
+    name = "${name}-backup";
+
+    nodes = {
+      master = { pkgs, ... }: {
+        services.mysql = {
+          inherit package;
+          enable = true;
+          initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+        };
+
+        services.mysqlBackup = {
+          enable = true;
+          databases = [ "doesnotexist" "testdb" ];
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # Delete backup file that may be left over from a previous test run.
+      # This is not needed on Hydra but useful for repeated local test runs.
+      master.execute("rm -f /var/backup/mysql/testdb.gz")
+
+      # Need to have mysql started so that it can be populated with data.
+      master.wait_for_unit("mysql.service")
+
+      # Wait for testdb to be fully populated (5 rows).
+      master.wait_until_succeeds(
+          "mysql -u root -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
+      )
+
+      # Do a backup and wait for it to start
+      master.start_job("mysql-backup.service")
+
+      # wait for backup to fail, because of database 'doesnotexist'
+      master.wait_until_fails("systemctl is-active -q mysql-backup.service")
+
+      # wait for backup file and check that data appears in backup
+      master.wait_for_file("/var/backup/mysql/testdb.gz")
+      master.succeed(
+          "${pkgs.gzip}/bin/zcat /var/backup/mysql/testdb.gz | grep hello"
+      )
+
+      # Check that a failed backup is logged
+      master.succeed(
+          "journalctl -u mysql-backup.service | grep 'fail.*doesnotexist' > /dev/null"
+      )
+    '';
+  };
+in
+  lib.mapAttrs (_: package: makeBackupTest { inherit package; }) mariadbPackages
diff --git a/nixpkgs/nixos/tests/mysql/mysql-replication.nix b/nixpkgs/nixos/tests/mysql/mysql-replication.nix
new file mode 100644
index 000000000000..83da1e7b6cb8
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql/mysql-replication.nix
@@ -0,0 +1,101 @@
+{
+  system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; },
+  lib ? pkgs.lib
+}:
+
+let
+  inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages;
+
+  replicateUser = "replicate";
+  replicatePassword = "secret";
+
+  makeTest = import ./../make-test-python.nix;
+
+  makeReplicationTest = {
+    package,
+    name ? mkTestName package,
+  }: makeTest {
+    name = "${name}-replication";
+    meta = {
+      maintainers = lib.teams.helsinki-systems.members;
+    };
+
+    nodes = {
+      primary = {
+        services.mysql = {
+          inherit package;
+          enable = true;
+          replication.role = "master";
+          replication.slaveHost = "%";
+          replication.masterUser = replicateUser;
+          replication.masterPassword = replicatePassword;
+          initialDatabases = [ { name = "testdb"; schema = ./testdb.sql; } ];
+        };
+        networking.firewall.allowedTCPPorts = [ 3306 ];
+      };
+
+      secondary1 = { nodes, ... }: {
+        services.mysql = {
+          inherit package;
+          enable = true;
+          replication.role = "slave";
+          replication.serverId = 2;
+          replication.masterHost = nodes.primary.networking.hostName;
+          replication.masterUser = replicateUser;
+          replication.masterPassword = replicatePassword;
+        };
+      };
+
+      secondary2 = { nodes, ... }: {
+        services.mysql = {
+          inherit package;
+          enable = true;
+          replication.role = "slave";
+          replication.serverId = 3;
+          replication.masterHost = nodes.primary.networking.hostName;
+          replication.masterUser = replicateUser;
+          replication.masterPassword = replicatePassword;
+        };
+      };
+    };
+
+    testScript = ''
+      primary.start()
+      primary.wait_for_unit("mysql")
+      primary.wait_for_open_port(3306)
+      # Wait for testdb to be fully populated (5 rows).
+      primary.wait_until_succeeds(
+          "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
+      )
+
+      secondary1.start()
+      secondary2.start()
+      secondary1.wait_for_unit("mysql")
+      secondary1.wait_for_open_port(3306)
+      secondary2.wait_for_unit("mysql")
+      secondary2.wait_for_open_port(3306)
+
+      # wait for replications to finish
+      secondary1.wait_until_succeeds(
+          "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
+      )
+      secondary2.wait_until_succeeds(
+          "sudo -u mysql mysql -u mysql -D testdb -N -B -e 'select count(id) from tests' | grep -q 5"
+      )
+
+      secondary2.succeed("systemctl stop mysql")
+      primary.succeed(
+          "echo 'insert into testdb.tests values (123, 456);' | sudo -u mysql mysql -u mysql -N"
+      )
+      secondary2.succeed("systemctl start mysql")
+      secondary2.wait_for_unit("mysql")
+      secondary2.wait_for_open_port(3306)
+      secondary2.wait_until_succeeds(
+          "echo 'select * from testdb.tests where Id = 123;' | sudo -u mysql mysql -u mysql -N | grep 456"
+      )
+    '';
+  };
+in
+  lib.mapAttrs (_: package: makeReplicationTest { inherit package; }) mariadbPackages
diff --git a/nixpkgs/nixos/tests/mysql/mysql.nix b/nixpkgs/nixos/tests/mysql/mysql.nix
new file mode 100644
index 000000000000..0a61f9d38fe2
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql/mysql.nix
@@ -0,0 +1,151 @@
+{
+  system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; },
+  lib ? pkgs.lib
+}:
+
+let
+  inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages mysqlPackages perconaPackages;
+
+  makeTest = import ./../make-test-python.nix;
+  # Setup common users
+  makeMySQLTest = {
+    package,
+    name ? mkTestName package,
+    useSocketAuth ? true,
+    hasMroonga ? true,
+    hasRocksDB ? pkgs.stdenv.hostPlatform.is64bit
+  }: makeTest {
+    inherit name;
+    meta = {
+      maintainers = lib.teams.helsinki-systems.members;
+    };
+
+    nodes = {
+      ${name} =
+        { pkgs, ... }: {
+
+          users = {
+            groups.testusers = { };
+
+            users.testuser = {
+              isSystemUser = true;
+              group = "testusers";
+            };
+
+            users.testuser2 = {
+              isSystemUser = true;
+              group = "testusers";
+            };
+          };
+
+          services.mysql = {
+            enable = true;
+            initialDatabases = [
+              { name = "testdb3"; schema = ./testdb.sql; }
+            ];
+            # note that using pkgs.writeText here is generally not a good idea,
+            # as it will store the password in world-readable /nix/store ;)
+            initialScript = pkgs.writeText "mysql-init.sql" (if (!useSocketAuth) then ''
+              CREATE USER 'testuser3'@'localhost' IDENTIFIED BY 'secure';
+              GRANT ALL PRIVILEGES ON testdb3.* TO 'testuser3'@'localhost';
+            '' else ''
+              ALTER USER root@localhost IDENTIFIED WITH unix_socket;
+              DELETE FROM mysql.user WHERE password = ''' AND plugin = ''';
+              DELETE FROM mysql.user WHERE user = ''';
+              FLUSH PRIVILEGES;
+            '');
+
+            ensureDatabases = [ "testdb" "testdb2" ];
+            ensureUsers = [{
+              name = "testuser";
+              ensurePermissions = {
+                "testdb.*" = "ALL PRIVILEGES";
+              };
+            } {
+              name = "testuser2";
+              ensurePermissions = {
+                "testdb2.*" = "ALL PRIVILEGES";
+              };
+            }];
+            package = package;
+            settings = {
+              mysqld = {
+                plugin-load-add = lib.optional hasMroonga "ha_mroonga.so"
+                  ++ lib.optional hasRocksDB "ha_rocksdb.so";
+              };
+            };
+          };
+        };
+    };
+
+    testScript = ''
+      start_all()
+
+      machine = ${name}
+      machine.wait_for_unit("mysql")
+      machine.succeed(
+          "echo 'use testdb; create table tests (test_id INT, PRIMARY KEY (test_id));' | sudo -u testuser mysql -u testuser"
+      )
+      machine.succeed(
+          "echo 'use testdb; insert into tests values (42);' | sudo -u testuser mysql -u testuser"
+      )
+      # Ensure testuser2 is not able to insert into testdb as mysql testuser2
+      machine.fail(
+          "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser2"
+      )
+      # Ensure testuser2 is not able to authenticate as mysql testuser
+      machine.fail(
+          "echo 'use testdb; insert into tests values (23);' | sudo -u testuser2 mysql -u testuser"
+      )
+      machine.succeed(
+          "echo 'use testdb; select test_id from tests;' | sudo -u testuser mysql -u testuser -N | grep 42"
+      )
+
+      ${lib.optionalString hasMroonga ''
+        # Check if Mroonga plugin works
+        machine.succeed(
+            "echo 'use testdb; create table mroongadb (test_id INT, PRIMARY KEY (test_id)) ENGINE = Mroonga;' | sudo -u testuser mysql -u testuser"
+        )
+        machine.succeed(
+            "echo 'use testdb; insert into mroongadb values (25);' | sudo -u testuser mysql -u testuser"
+        )
+        machine.succeed(
+            "echo 'use testdb; select test_id from mroongadb;' | sudo -u testuser mysql -u testuser -N | grep 25"
+        )
+        machine.succeed(
+            "echo 'use testdb; drop table mroongadb;' | sudo -u testuser mysql -u testuser"
+        )
+      ''}
+
+      ${lib.optionalString hasRocksDB ''
+        # Check if RocksDB plugin works
+        machine.succeed(
+            "echo 'use testdb; create table rocksdb (test_id INT, PRIMARY KEY (test_id)) ENGINE = RocksDB;' | sudo -u testuser mysql -u testuser"
+        )
+        machine.succeed(
+            "echo 'use testdb; insert into rocksdb values (28);' | sudo -u testuser mysql -u testuser"
+        )
+        machine.succeed(
+            "echo 'use testdb; select test_id from rocksdb;' | sudo -u testuser mysql -u testuser -N | grep 28"
+        )
+        machine.succeed(
+            "echo 'use testdb; drop table rocksdb;' | sudo -u testuser mysql -u testuser"
+        )
+      ''}
+    '';
+  };
+in
+  lib.mapAttrs (_: package: makeMySQLTest {
+    inherit package;
+    hasRocksDB = false; hasMroonga = false; useSocketAuth = false;
+  }) mysqlPackages
+  // (lib.mapAttrs (_: package: makeMySQLTest {
+    inherit package;
+  }) mariadbPackages)
+  // (lib.mapAttrs (_: package: makeMySQLTest {
+    inherit package;
+    name = "percona_8_0";
+    hasMroonga = false; useSocketAuth = false;
+  }) perconaPackages)
diff --git a/nixpkgs/nixos/tests/mysql/testdb.sql b/nixpkgs/nixos/tests/mysql/testdb.sql
new file mode 100644
index 000000000000..3c68c49ae82c
--- /dev/null
+++ b/nixpkgs/nixos/tests/mysql/testdb.sql
@@ -0,0 +1,11 @@
+create table tests
+( Id   INTEGER      NOT NULL,
+  Name VARCHAR(255) NOT NULL,
+  primary key(Id)
+);
+
+insert into tests values (1, 'a');
+insert into tests values (2, 'b');
+insert into tests values (3, 'c');
+insert into tests values (4, 'd');
+insert into tests values (5, 'hello');
diff --git a/nixpkgs/nixos/tests/n8n.nix b/nixpkgs/nixos/tests/n8n.nix
new file mode 100644
index 000000000000..0a12192d5c71
--- /dev/null
+++ b/nixpkgs/nixos/tests/n8n.nix
@@ -0,0 +1,25 @@
+import ./make-test-python.nix ({ lib, ... }:
+let
+  port = 5678;
+  webhookUrl = "http://example.com";
+in
+{
+  name = "n8n";
+  meta.maintainers = with lib.maintainers; [ freezeboy k900 ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.n8n = {
+        enable = true;
+        webhookUrl = webhookUrl;
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("n8n.service")
+    machine.wait_for_console_text("Editor is now accessible via")
+    machine.succeed("curl --fail -vvv http://localhost:${toString port}/")
+    machine.succeed("grep -qF ${webhookUrl} /etc/systemd/system/n8n.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nagios.nix b/nixpkgs/nixos/tests/nagios.nix
new file mode 100644
index 000000000000..b6e45fc103af
--- /dev/null
+++ b/nixpkgs/nixos/tests/nagios.nix
@@ -0,0 +1,116 @@
+import ./make-test-python.nix (
+  { pkgs, ... }: {
+    name = "nagios";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ symphorien ];
+    };
+
+    nodes.machine = { lib, ... }: let
+      writer = pkgs.writeShellScript "write" ''
+        set -x
+        echo "$@"  >> /tmp/notifications
+      '';
+    in
+      {
+        # tested service
+        services.sshd.enable = true;
+        # nagios
+        services.nagios = {
+          enable = true;
+          # make state transitions faster
+          extraConfig.interval_length = "5";
+          objectDefs =
+            (map (x: "${pkgs.nagios}/etc/objects/${x}.cfg") [ "templates" "timeperiods" "commands" ]) ++ [
+              (
+                pkgs.writeText "objects.cfg" ''
+                  # notifications are written to /tmp/notifications
+                  define command {
+                  command_name notify-host-by-file
+                  command_line ${writer} "$HOSTNAME is $HOSTSTATE$"
+                  }
+                  define command {
+                  command_name notify-service-by-file
+                  command_line ${writer} "$SERVICEDESC$ is $SERVICESTATE$"
+                  }
+
+                  # nagios boilerplate
+                  define contact {
+                  contact_name                    alice
+                  alias                           alice
+                  host_notifications_enabled      1
+                  service_notifications_enabled   1
+                  service_notification_period     24x7
+                  host_notification_period        24x7
+                  service_notification_options    w,u,c,r,f,s
+                  host_notification_options       d,u,r,f,s
+                  service_notification_commands   notify-service-by-file
+                  host_notification_commands      notify-host-by-file
+                  email                           foo@example.com
+                  }
+                  define contactgroup {
+                  contactgroup_name   admins
+                  alias               Admins
+                  members alice
+                  }
+                  define hostgroup{
+                  hostgroup_name  allhosts
+                  alias  All hosts
+                  }
+
+                  # monitored objects
+                  define host {
+                  use         generic-host
+                  host_name   localhost
+                  alias       localhost
+                  address     localhost
+                  hostgroups  allhosts
+                  contact_groups admins
+                  # make state transitions faster.
+                  max_check_attempts 2
+                  check_interval 1
+                  retry_interval 1
+                  }
+                  define service {
+                  use                 generic-service
+                  host_name           localhost
+                  service_description ssh
+                  check_command       check_ssh
+                  # make state transitions faster.
+                  max_check_attempts 2
+                  check_interval 1
+                  retry_interval 1
+                  }
+                ''
+              )
+            ];
+        };
+      };
+
+    testScript = { ... }: ''
+      with subtest("ensure sshd starts"):
+          machine.wait_for_unit("sshd.service")
+
+
+      with subtest("ensure nagios starts"):
+          machine.wait_for_file("/var/log/nagios/current")
+
+
+      def assert_notify(text):
+          machine.wait_for_file("/tmp/notifications")
+          real = machine.succeed("cat /tmp/notifications").strip()
+          print(f"got {real!r}, expected {text!r}")
+          assert text == real
+
+
+      with subtest("ensure we get a notification when sshd is down"):
+          machine.succeed("systemctl stop sshd")
+          assert_notify("ssh is CRITICAL")
+
+
+      with subtest("ensure tests can succeed"):
+          machine.succeed("systemctl start sshd")
+          machine.succeed("rm /tmp/notifications")
+          assert_notify("ssh is OK")
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/nar-serve.nix b/nixpkgs/nixos/tests/nar-serve.nix
new file mode 100644
index 000000000000..f6197567b822
--- /dev/null
+++ b/nixpkgs/nixos/tests/nar-serve.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  {
+    name = "nar-serve";
+    meta.maintainers = [ lib.maintainers.rizary ];
+    nodes =
+      {
+        server = { pkgs, ... }: {
+          services.nginx = {
+            enable = true;
+            virtualHosts.default.root = "/var/www";
+          };
+          services.nar-serve = {
+            enable = true;
+            # Connect to the localhost nginx instead of the default
+            # https://cache.nixos.org
+            cacheURL = "http://localhost/";
+          };
+          environment.systemPackages = [
+            pkgs.hello
+            pkgs.curl
+          ];
+
+          networking.firewall.allowedTCPPorts = [ 8383 ];
+
+          # virtualisation.diskSize = 2 * 1024;
+        };
+      };
+    testScript = ''
+      import os
+
+      start_all()
+
+      # Create a fake cache with Nginx service the static files
+      server.succeed(
+          "nix --experimental-features nix-command copy --to file:///var/www ${pkgs.hello}"
+      )
+      server.wait_for_unit("nginx.service")
+      server.wait_for_open_port(80)
+
+      # Check that nar-serve can return the content of the derivation
+      drvName = os.path.basename("${pkgs.hello}")
+      drvHash = drvName.split("-")[0]
+      server.wait_for_unit("nar-serve.service")
+      server.succeed(
+          "curl -o hello -f http://localhost:8383/nix/store/{}/bin/hello".format(drvHash)
+      )
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/nat.nix b/nixpkgs/nixos/tests/nat.nix
new file mode 100644
index 000000000000..0b617cea7774
--- /dev/null
+++ b/nixpkgs/nixos/tests/nat.nix
@@ -0,0 +1,115 @@
+# This is a simple distributed test involving a topology with two
+# separate virtual networks - the "inside" and the "outside" - with a
+# client on the inside network, a server on the outside network, and a
+# router connected to both that performs Network Address Translation
+# for the client.
+import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ... }:
+  let
+    unit = if nftables then "nftables" else (if withFirewall then "firewall" else "nat");
+
+    routerBase =
+      lib.mkMerge [
+        { virtualisation.vlans = [ 2 1 ];
+          networking.firewall.enable = withFirewall;
+          networking.firewall.filterForward = nftables;
+          networking.nftables.enable = nftables;
+          networking.nat.internalIPs = [ "192.168.1.0/24" ];
+          networking.nat.externalInterface = "eth1";
+        }
+      ];
+  in
+  {
+    name = "nat" + (lib.optionalString nftables "Nftables")
+                 + (if withFirewall then "WithFirewall" else "Standalone");
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ eelco rob ];
+    };
+
+    nodes =
+      { client =
+          { pkgs, nodes, ... }:
+          lib.mkMerge [
+            { virtualisation.vlans = [ 1 ];
+              networking.defaultGateway =
+                (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
+              networking.nftables.enable = nftables;
+            }
+          ];
+
+        router =
+        { ... }: lib.mkMerge [
+          routerBase
+          { networking.nat.enable = true; }
+        ];
+
+        routerDummyNoNat =
+        { ... }: lib.mkMerge [
+          routerBase
+          { networking.nat.enable = false; }
+        ];
+
+        server =
+          { ... }:
+          { virtualisation.vlans = [ 2 ];
+            networking.firewall.enable = false;
+            services.httpd.enable = true;
+            services.httpd.adminAddr = "foo@example.org";
+            services.vsftpd.enable = true;
+            services.vsftpd.anonymousUser = true;
+          };
+      };
+
+    testScript =
+      { nodes, ... }: let
+        routerDummyNoNatClosure = nodes.routerDummyNoNat.config.system.build.toplevel;
+        routerClosure = nodes.router.config.system.build.toplevel;
+      in ''
+        client.start()
+        router.start()
+        server.start()
+
+        # The router should have access to the server.
+        server.wait_for_unit("network.target")
+        server.wait_for_unit("httpd")
+        router.wait_for_unit("network.target")
+        router.succeed("curl --fail http://server/ >&2")
+
+        # The client should be also able to connect via the NAT router.
+        router.wait_for_unit("${unit}")
+        client.wait_for_unit("network.target")
+        client.succeed("curl --fail http://server/ >&2")
+        client.succeed("ping -c 1 server >&2")
+
+        # Test whether passive FTP works.
+        server.wait_for_unit("vsftpd")
+        server.succeed("echo Hello World > /home/ftp/foo.txt")
+        client.succeed("curl -v ftp://server/foo.txt >&2")
+
+        # Test whether active FTP works.
+        client.fail("curl -v -P - ftp://server/foo.txt >&2")
+
+        # Test ICMP.
+        client.succeed("ping -c 1 router >&2")
+        router.succeed("ping -c 1 client >&2")
+
+        # If we turn off NAT, the client shouldn't be able to reach the server.
+        router.succeed(
+            "${routerDummyNoNatClosure}/bin/switch-to-configuration test 2>&1"
+        )
+        client.fail("curl --fail --connect-timeout 5 http://server/ >&2")
+        client.fail("ping -c 1 server >&2")
+
+        # And make sure that reloading the NAT job works.
+        router.succeed(
+            "${routerClosure}/bin/switch-to-configuration test 2>&1"
+        )
+        # FIXME: this should not be necessary, but nat.service is not started because
+        #        network.target is not triggered
+        #        (https://github.com/NixOS/nixpkgs/issues/16230#issuecomment-226408359)
+        ${lib.optionalString (!withFirewall && !nftables) ''
+          router.succeed("systemctl start nat.service")
+        ''}
+        client.succeed("curl --fail http://server/ >&2")
+        client.succeed("ping -c 1 server >&2")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/nats.nix b/nixpkgs/nixos/tests/nats.nix
new file mode 100644
index 000000000000..c650904e53bf
--- /dev/null
+++ b/nixpkgs/nixos/tests/nats.nix
@@ -0,0 +1,63 @@
+let
+
+  port = 4222;
+  username = "client";
+  password = "password";
+  topic = "foo.bar";
+
+in import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "nats";
+  meta = with pkgs.lib; { maintainers = with maintainers; [ c0deaddict ]; };
+
+  nodes = let
+    client = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ natscli ];
+    };
+  in {
+    server = { pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ port ];
+      services.nats = {
+        inherit port;
+        enable = true;
+        settings = {
+          authorization = {
+            users = [{
+              user = username;
+              inherit password;
+            }];
+          };
+        };
+      };
+    };
+
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = let file = "/tmp/msg";
+  in ''
+    def nats_cmd(*args):
+        return (
+            "nats "
+            "--server=nats://server:${toString port} "
+            "--user=${username} "
+            "--password=${password} "
+            "{}"
+        ).format(" ".join(args))
+
+    def parallel(*fns):
+        from threading import Thread
+        threads = [ Thread(target=fn) for fn in fns ]
+        for t in threads: t.start()
+        for t in threads: t.join()
+
+    start_all()
+    server.wait_for_unit("nats.service")
+
+    with subtest("pub sub"):
+        parallel(
+            lambda: client1.succeed(nats_cmd("sub", "--count", "1", "${topic}")),
+            lambda: client2.succeed("sleep 2 && {}".format(nats_cmd("pub", "${topic}", "hello"))),
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/navidrome.nix b/nixpkgs/nixos/tests/navidrome.nix
new file mode 100644
index 000000000000..7315aef62401
--- /dev/null
+++ b/nixpkgs/nixos/tests/navidrome.nix
@@ -0,0 +1,12 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "navidrome";
+
+  nodes.machine = { ... }: {
+    services.navidrome.enable = true;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("navidrome")
+    machine.wait_for_open_port(4533)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nbd.nix b/nixpkgs/nixos/tests/nbd.nix
new file mode 100644
index 000000000000..b4aaf29ee4e5
--- /dev/null
+++ b/nixpkgs/nixos/tests/nbd.nix
@@ -0,0 +1,103 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    listenPort = 30123;
+    testString = "It works!";
+    mkCreateSmallFileService = { path, loop ? false }: {
+      script = ''
+        ${pkgs.coreutils}/bin/dd if=/dev/zero of=${path} bs=1K count=100
+        ${pkgs.lib.optionalString loop
+          "${pkgs.util-linux}/bin/losetup --find ${path}"}
+      '';
+      serviceConfig = {
+        Type = "oneshot";
+      };
+      wantedBy = [ "multi-user.target" ];
+      before = [ "nbd-server.service" ];
+    };
+  in
+  {
+    name = "nbd";
+
+    nodes = {
+      server = { config, pkgs, ... }: {
+        # Create some small files of zeros to use as the ndb disks
+        ## `vault-pub.disk` is accessible from any IP
+        systemd.services.create-pub-file =
+          mkCreateSmallFileService { path = "/vault-pub.disk"; };
+        ## `vault-priv.disk` is accessible only from localhost.
+        ## It's also a loopback device to test exporting /dev/...
+        systemd.services.create-priv-file =
+          mkCreateSmallFileService { path = "/vault-priv.disk"; loop = true; };
+        ## `aaa.disk` is just here because "[aaa]" sorts before
+        ## "[generic]" lexicographically, and nbd-server breaks if
+        ## "[generic]" isn't the first section.
+        systemd.services.create-aaa-file =
+          mkCreateSmallFileService { path = "/aaa.disk"; };
+
+        # Needed only for nbd-client used in the tests.
+        environment.systemPackages = [ pkgs.nbd ];
+
+        # Open the nbd port in the firewall
+        networking.firewall.allowedTCPPorts = [ listenPort ];
+
+        # Run the nbd server and expose the small file created above
+        services.nbd.server = {
+          enable = true;
+          exports = {
+            aaa = {
+              path = "/aaa.disk";
+            };
+            vault-pub = {
+              path = "/vault-pub.disk";
+            };
+            vault-priv = {
+              path = "/dev/loop0";
+              allowAddresses = [ "127.0.0.1" "::1" ];
+            };
+          };
+          listenAddress = "0.0.0.0";
+          listenPort = listenPort;
+        };
+      };
+
+      client = { config, pkgs, ... }: {
+        programs.nbd.enable = true;
+      };
+    };
+
+    testScript = ''
+      testString = "${testString}"
+
+      start_all()
+      server.wait_for_open_port(${toString listenPort})
+
+      # Client: Connect to the server, write a small string to the nbd disk, and cleanly disconnect
+      client.succeed("nbd-client server ${toString listenPort} /dev/nbd0 -name vault-pub -persist")
+      client.succeed(f"echo '{testString}' | dd of=/dev/nbd0 conv=notrunc")
+      client.succeed("nbd-client -d /dev/nbd0")
+
+      # Server: Check that the string written by the client is indeed in the file
+      foundString = server.succeed(f"dd status=none if=/vault-pub.disk count={len(testString)}")[:len(testString)]
+      if foundString != testString:
+         raise Exception(f"Read the wrong string from nbd disk. Expected: '{testString}'. Found: '{foundString}'")
+
+      # Client: Fail to connect to the private disk
+      client.fail("nbd-client server ${toString listenPort} /dev/nbd0 -name vault-priv -persist")
+
+      # Server: Successfully connect to the private disk
+      server.succeed("nbd-client localhost ${toString listenPort} /dev/nbd0 -name vault-priv -persist")
+      server.succeed(f"echo '{testString}' | dd of=/dev/nbd0 conv=notrunc")
+      foundString = server.succeed(f"dd status=none if=/dev/loop0 count={len(testString)}")[:len(testString)]
+      if foundString != testString:
+         raise Exception(f"Read the wrong string from nbd disk. Expected: '{testString}'. Found: '{foundString}'")
+      server.succeed("nbd-client -d /dev/nbd0")
+
+      # Server: Successfully connect to the aaa disk
+      server.succeed("nbd-client localhost ${toString listenPort} /dev/nbd0 -name aaa -persist")
+      server.succeed(f"echo '{testString}' | dd of=/dev/nbd0 conv=notrunc")
+      foundString = server.succeed(f"dd status=none if=/aaa.disk count={len(testString)}")[:len(testString)]
+      if foundString != testString:
+         raise Exception(f"Read the wrong string from nbd disk. Expected: '{testString}'. Found: '{foundString}'")
+      server.succeed("nbd-client -d /dev/nbd0")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/ncdns.nix b/nixpkgs/nixos/tests/ncdns.nix
new file mode 100644
index 000000000000..3ce39ed3cb55
--- /dev/null
+++ b/nixpkgs/nixos/tests/ncdns.nix
@@ -0,0 +1,93 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+let
+  fakeReply = pkgs.writeText "namecoin-reply.json" ''
+  { "error": null,
+    "id": 1,
+    "result": {
+      "address": "T31q8ucJ4dI1xzhxQ5QispfECld5c7Xw",
+      "expired": false,
+      "expires_in": 2248,
+      "height": 438155,
+      "name": "d/test",
+      "txid": "db61c0b2540ba0c1a2c8cc92af703a37002e7566ecea4dbf8727c7191421edfb",
+      "value": "{\"ip\": \"1.2.3.4\", \"email\": \"root@test.bit\",\"info\": \"Fake record\"}",
+      "vout": 0
+    }
+  }
+  '';
+
+  # Disabled because DNSSEC does not currently validate,
+  # see https://github.com/namecoin/ncdns/issues/127
+  dnssec = false;
+
+in
+
+{
+  name = "ncdns";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  nodes.server = { ... }: {
+    networking.nameservers = [ "::1" ];
+
+    services.namecoind.rpc = {
+      address = "::1";
+      user = "namecoin";
+      password = "secret";
+      port = 8332;
+    };
+
+    # Fake namecoin RPC server because we can't
+    # run a full node in a test.
+    systemd.services.namecoind = {
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        while true; do
+          echo -e "HTTP/1.1 200 OK\n\n $(<${fakeReply})\n" \
+            | ${pkgs.netcat}/bin/nc -N -l ::1 8332
+        done
+      '';
+    };
+
+    services.ncdns = {
+      enable = true;
+      dnssec.enable = dnssec;
+      identity.hostname   = "example.com";
+      identity.hostmaster = "root@example.com";
+      identity.address    = "1.0.0.1";
+    };
+
+    services.pdns-recursor.enable = true;
+    services.pdns-recursor.resolveNamecoin = true;
+
+    environment.systemPackages = [ pkgs.dnsutils ];
+  };
+
+  testScript =
+    (lib.optionalString dnssec ''
+      with subtest("DNSSEC keys have been generated"):
+          server.wait_for_unit("ncdns")
+          server.wait_for_file("/var/lib/ncdns/bit.key")
+          server.wait_for_file("/var/lib/ncdns/bit-zone.key")
+
+      with subtest("DNSKEY bit record is present"):
+          server.wait_for_unit("pdns-recursor")
+          server.wait_for_open_port(53)
+          server.succeed("host -t DNSKEY bit")
+    '') +
+    ''
+      with subtest("can resolve a .bit name"):
+          server.wait_for_unit("namecoind")
+          server.wait_for_unit("ncdns")
+          server.wait_for_open_port(8332)
+          assert "1.2.3.4" in server.succeed("dig @localhost -p 5333 test.bit")
+
+      with subtest("SOA record has identity information"):
+          assert "example.com" in server.succeed("dig SOA @localhost -p 5333 bit")
+
+      with subtest("bit. zone forwarding works"):
+          server.wait_for_unit("pdns-recursor")
+          assert "1.2.3.4" in server.succeed("host test.bit")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/ndppd.nix b/nixpkgs/nixos/tests/ndppd.nix
new file mode 100644
index 000000000000..e79e2a097b40
--- /dev/null
+++ b/nixpkgs/nixos/tests/ndppd.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "ndppd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fpletz ];
+  };
+
+  nodes = {
+    upstream = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.tcpdump ];
+      networking.useDHCP = false;
+      networking.interfaces = {
+        eth1 = {
+          ipv6.addresses = [
+            { address = "fd23::1"; prefixLength = 112; }
+          ];
+          ipv6.routes = [
+            { address = "fd42::";
+              prefixLength = 112;
+            }
+          ];
+        };
+      };
+    };
+    server = { pkgs, ... }: {
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.all.forwarding" = "1";
+        "net.ipv6.conf.default.forwarding" = "1";
+      };
+      environment.systemPackages = [ pkgs.tcpdump ];
+      networking.useDHCP = false;
+      networking.interfaces = {
+        eth1 = {
+          ipv6.addresses = [
+            { address = "fd23::2"; prefixLength = 112; }
+          ];
+        };
+      };
+      services.ndppd = {
+        enable = true;
+        proxies.eth1.rules."fd42::/112" = {};
+      };
+      containers.client = {
+        autoStart = true;
+        privateNetwork = true;
+        hostAddress = "192.168.255.1";
+        localAddress = "192.168.255.2";
+        hostAddress6 = "fd42::1";
+        localAddress6 = "fd42::2";
+        config = {};
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("multi-user.target")
+    upstream.wait_for_unit("multi-user.target")
+    upstream.wait_until_succeeds("ping -c5 fd42::2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nebula.nix b/nixpkgs/nixos/tests/nebula.nix
new file mode 100644
index 000000000000..89b91d89fcb3
--- /dev/null
+++ b/nixpkgs/nixos/tests/nebula.nix
@@ -0,0 +1,308 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+
+  # We'll need to be able to trade cert files between nodes via scp.
+  inherit (import ./ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+
+  makeNebulaNode = { config, ... }: name: extraConfig: lib.mkMerge [
+    {
+      # Expose nebula for doing cert signing.
+      environment.systemPackages = [ pkgs.nebula ];
+      users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+      services.openssh.enable = true;
+      networking.interfaces.eth1.useDHCP = false;
+
+      services.nebula.networks.smoke = {
+        # Note that these paths won't exist when the machine is first booted.
+        ca = "/etc/nebula/ca.crt";
+        cert = "/etc/nebula/${name}.crt";
+        key = "/etc/nebula/${name}.key";
+        listen = { host = "0.0.0.0"; port = 4242; };
+      };
+    }
+    extraConfig
+  ];
+
+in
+{
+  name = "nebula";
+
+  nodes = {
+
+    lighthouse = { ... } @ args:
+      makeNebulaNode args "lighthouse" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
+          address = "192.168.1.1";
+          prefixLength = 24;
+        }];
+
+        services.nebula.networks.smoke = {
+          isLighthouse = true;
+          isRelay = true;
+          firewall = {
+            outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
+            inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
+          };
+        };
+      };
+
+    allowAny = { ... } @ args:
+      makeNebulaNode args "allowAny" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
+          address = "192.168.1.2";
+          prefixLength = 24;
+        }];
+
+        services.nebula.networks.smoke = {
+          staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; };
+          isLighthouse = false;
+          lighthouses = [ "10.0.100.1" ];
+          relays = [ "10.0.100.1" ];
+          firewall = {
+            outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
+            inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
+          };
+        };
+      };
+
+    allowFromLighthouse = { ... } @ args:
+      makeNebulaNode args "allowFromLighthouse" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
+          address = "192.168.1.3";
+          prefixLength = 24;
+        }];
+
+        services.nebula.networks.smoke = {
+          staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; };
+          isLighthouse = false;
+          lighthouses = [ "10.0.100.1" ];
+          relays = [ "10.0.100.1" ];
+          firewall = {
+            outbound = [ { port = "any"; proto = "any"; host = "any"; } ];
+            inbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ];
+          };
+        };
+      };
+
+    allowToLighthouse = { ... } @ args:
+      makeNebulaNode args "allowToLighthouse" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
+          address = "192.168.1.4";
+          prefixLength = 24;
+        }];
+
+        services.nebula.networks.smoke = {
+          enable = true;
+          staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; };
+          isLighthouse = false;
+          lighthouses = [ "10.0.100.1" ];
+          relays = [ "10.0.100.1" ];
+          firewall = {
+            outbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ];
+            inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
+          };
+        };
+      };
+
+    disabled = { ... } @ args:
+      makeNebulaNode args "disabled" {
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
+          address = "192.168.1.5";
+          prefixLength = 24;
+        }];
+
+        services.nebula.networks.smoke = {
+          enable = false;
+          staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; };
+          isLighthouse = false;
+          lighthouses = [ "10.0.100.1" ];
+          relays = [ "10.0.100.1" ];
+          firewall = {
+            outbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ];
+            inbound = [ { port = "any"; proto = "any"; host = "any"; } ];
+          };
+        };
+      };
+
+  };
+
+  testScript = let
+
+    setUpPrivateKey = name: ''
+      ${name}.start()
+      ${name}.succeed(
+          "mkdir -p /root/.ssh",
+          "chown 700 /root/.ssh",
+          "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil",
+          "chown 600 /root/.ssh/id_snakeoil",
+          "mkdir -p /root"
+      )
+    '';
+
+    # From what I can tell, StrictHostKeyChecking=no is necessary for ssh to work between machines.
+    sshOpts = "-oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oIdentityFile=/root/.ssh/id_snakeoil";
+
+    restartAndCheckNebula = name: ip: ''
+      ${name}.systemctl("restart nebula@smoke.service")
+      ${name}.succeed("ping -c5 ${ip}")
+    '';
+
+    # Create a keypair on the client node, then use the public key to sign a cert on the lighthouse.
+    signKeysFor = name: ip: ''
+      lighthouse.wait_for_unit("sshd.service")
+      ${name}.wait_for_unit("sshd.service")
+      ${name}.succeed(
+          "mkdir -p /etc/nebula",
+          "nebula-cert keygen -out-key /etc/nebula/${name}.key -out-pub /etc/nebula/${name}.pub",
+          "scp ${sshOpts} /etc/nebula/${name}.pub root@192.168.1.1:/root/${name}.pub",
+      )
+      lighthouse.succeed(
+          'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "${name}" -groups "${name}" -ip "${ip}" -in-pub /root/${name}.pub -out-crt /root/${name}.crt'
+      )
+      ${name}.succeed(
+          "scp ${sshOpts} root@192.168.1.1:/root/${name}.crt /etc/nebula/${name}.crt",
+          "scp ${sshOpts} root@192.168.1.1:/etc/nebula/ca.crt /etc/nebula/ca.crt",
+          '(id nebula-smoke >/dev/null && chown -R nebula-smoke:nebula-smoke /etc/nebula) || true'
+      )
+    '';
+
+    getPublicIp = node: ''
+      ${node}.succeed("ip --brief addr show eth1 | awk '{print $3}' | tail -n1 | cut -d/ -f1").strip()
+    '';
+
+    # Never do this for anything security critical! (Thankfully it's just a test.)
+    # Restart Nebula right after the mutual block and/or restore so the state is fresh.
+    blockTrafficBetween = nodeA: nodeB: ''
+      node_a = ${getPublicIp nodeA}
+      node_b = ${getPublicIp nodeB}
+      ${nodeA}.succeed("iptables -I INPUT -s " + node_b + " -j DROP")
+      ${nodeB}.succeed("iptables -I INPUT -s " + node_a + " -j DROP")
+      ${nodeA}.systemctl("restart nebula@smoke.service")
+      ${nodeB}.systemctl("restart nebula@smoke.service")
+    '';
+    allowTrafficBetween = nodeA: nodeB: ''
+      node_a = ${getPublicIp nodeA}
+      node_b = ${getPublicIp nodeB}
+      ${nodeA}.succeed("iptables -D INPUT -s " + node_b + " -j DROP")
+      ${nodeB}.succeed("iptables -D INPUT -s " + node_a + " -j DROP")
+      ${nodeA}.systemctl("restart nebula@smoke.service")
+      ${nodeB}.systemctl("restart nebula@smoke.service")
+    '';
+  in ''
+    # Create the certificate and sign the lighthouse's keys.
+    ${setUpPrivateKey "lighthouse"}
+    lighthouse.succeed(
+        "mkdir -p /etc/nebula",
+        'nebula-cert ca -name "Smoke Test" -out-crt /etc/nebula/ca.crt -out-key /etc/nebula/ca.key',
+        'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "lighthouse" -groups "lighthouse" -ip "10.0.100.1/24" -out-crt /etc/nebula/lighthouse.crt -out-key /etc/nebula/lighthouse.key',
+        'chown -R nebula-smoke:nebula-smoke /etc/nebula'
+    )
+
+    # Reboot the lighthouse and verify that the nebula service comes up on boot.
+    # Since rebooting takes a while, we'll just restart the service on the other nodes.
+    lighthouse.shutdown()
+    lighthouse.start()
+    lighthouse.wait_for_unit("nebula@smoke.service")
+    lighthouse.succeed("ping -c5 10.0.100.1")
+
+    # Create keys for allowAny's nebula service and test that it comes up.
+    ${setUpPrivateKey "allowAny"}
+    ${signKeysFor "allowAny" "10.0.100.2/24"}
+    ${restartAndCheckNebula "allowAny" "10.0.100.2"}
+
+    # Create keys for allowFromLighthouse's nebula service and test that it comes up.
+    ${setUpPrivateKey "allowFromLighthouse"}
+    ${signKeysFor "allowFromLighthouse" "10.0.100.3/24"}
+    ${restartAndCheckNebula "allowFromLighthouse" "10.0.100.3"}
+
+    # Create keys for allowToLighthouse's nebula service and test that it comes up.
+    ${setUpPrivateKey "allowToLighthouse"}
+    ${signKeysFor "allowToLighthouse" "10.0.100.4/24"}
+    ${restartAndCheckNebula "allowToLighthouse" "10.0.100.4"}
+
+    # Create keys for disabled's nebula service and test that it does not come up.
+    ${setUpPrivateKey "disabled"}
+    ${signKeysFor "disabled" "10.0.100.5/24"}
+    disabled.fail("systemctl status nebula@smoke.service")
+    disabled.fail("ping -c5 10.0.100.5")
+
+    # The lighthouse can ping allowAny and allowFromLighthouse but not disabled
+    lighthouse.succeed("ping -c3 10.0.100.2")
+    lighthouse.succeed("ping -c3 10.0.100.3")
+    lighthouse.fail("ping -c3 10.0.100.5")
+
+    # allowAny can ping the lighthouse, but not allowFromLighthouse because of its inbound firewall
+    allowAny.succeed("ping -c3 10.0.100.1")
+    allowAny.fail("ping -c3 10.0.100.3")
+
+    # allowFromLighthouse can ping the lighthouse and allowAny
+    allowFromLighthouse.succeed("ping -c3 10.0.100.1")
+    allowFromLighthouse.succeed("ping -c3 10.0.100.2")
+
+    # block allowFromLighthouse <-> allowAny, and allowFromLighthouse -> allowAny should still work.
+    ${blockTrafficBetween "allowFromLighthouse" "allowAny"}
+    allowFromLighthouse.succeed("ping -c10 10.0.100.2")
+    ${allowTrafficBetween "allowFromLighthouse" "allowAny"}
+    allowFromLighthouse.succeed("ping -c10 10.0.100.2")
+
+    # allowToLighthouse can ping the lighthouse but not allowAny or allowFromLighthouse
+    allowToLighthouse.succeed("ping -c3 10.0.100.1")
+    allowToLighthouse.fail("ping -c3 10.0.100.2")
+    allowToLighthouse.fail("ping -c3 10.0.100.3")
+
+    # allowAny can ping allowFromLighthouse now that allowFromLighthouse pinged it first
+    allowAny.succeed("ping -c3 10.0.100.3")
+
+    # block allowAny <-> allowFromLighthouse, and allowAny -> allowFromLighthouse should still work.
+    ${blockTrafficBetween "allowAny" "allowFromLighthouse"}
+    allowFromLighthouse.succeed("ping -c10 10.0.100.2")
+    allowAny.succeed("ping -c10 10.0.100.3")
+    ${allowTrafficBetween "allowAny" "allowFromLighthouse"}
+    allowFromLighthouse.succeed("ping -c10 10.0.100.2")
+    allowAny.succeed("ping -c10 10.0.100.3")
+
+    # allowToLighthouse can ping allowAny if allowAny pings it first
+    allowAny.succeed("ping -c3 10.0.100.4")
+    allowToLighthouse.succeed("ping -c3 10.0.100.2")
+
+    # block allowToLighthouse <-> allowAny, and allowAny <-> allowToLighthouse should still work.
+    ${blockTrafficBetween "allowAny" "allowToLighthouse"}
+    allowAny.succeed("ping -c10 10.0.100.4")
+    allowToLighthouse.succeed("ping -c10 10.0.100.2")
+    ${allowTrafficBetween "allowAny" "allowToLighthouse"}
+    allowAny.succeed("ping -c10 10.0.100.4")
+    allowToLighthouse.succeed("ping -c10 10.0.100.2")
+
+    # block lighthouse <-> allowFromLighthouse and allowAny <-> allowFromLighthouse; allowFromLighthouse won't get to allowAny
+    ${blockTrafficBetween "allowFromLighthouse" "lighthouse"}
+    ${blockTrafficBetween "allowFromLighthouse" "allowAny"}
+    allowFromLighthouse.fail("ping -c3 10.0.100.2")
+    ${allowTrafficBetween "allowFromLighthouse" "lighthouse"}
+    ${allowTrafficBetween "allowFromLighthouse" "allowAny"}
+    allowFromLighthouse.succeed("ping -c3 10.0.100.2")
+
+    # block lighthouse <-> allowAny, allowAny <-> allowFromLighthouse, and allowAny <-> allowToLighthouse; it won't get to allowFromLighthouse or allowToLighthouse
+    ${blockTrafficBetween "allowAny" "lighthouse"}
+    ${blockTrafficBetween "allowAny" "allowFromLighthouse"}
+    ${blockTrafficBetween "allowAny" "allowToLighthouse"}
+    allowFromLighthouse.fail("ping -c3 10.0.100.2")
+    allowAny.fail("ping -c3 10.0.100.3")
+    allowAny.fail("ping -c3 10.0.100.4")
+    ${allowTrafficBetween "allowAny" "lighthouse"}
+    ${allowTrafficBetween "allowAny" "allowFromLighthouse"}
+    ${allowTrafficBetween "allowAny" "allowToLighthouse"}
+    allowFromLighthouse.succeed("ping -c3 10.0.100.2")
+    allowAny.succeed("ping -c3 10.0.100.3")
+    allowAny.succeed("ping -c3 10.0.100.4")
+
+    # block lighthouse <-> allowToLighthouse and allowToLighthouse <-> allowAny; it won't get to allowAny
+    ${blockTrafficBetween "allowToLighthouse" "lighthouse"}
+    ${blockTrafficBetween "allowToLighthouse" "allowAny"}
+    allowAny.fail("ping -c3 10.0.100.4")
+    allowToLighthouse.fail("ping -c3 10.0.100.2")
+    ${allowTrafficBetween "allowToLighthouse" "lighthouse"}
+    ${allowTrafficBetween "allowToLighthouse" "allowAny"}
+    allowAny.succeed("ping -c3 10.0.100.4")
+    allowToLighthouse.succeed("ping -c3 10.0.100.2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/neo4j.nix b/nixpkgs/nixos/tests/neo4j.nix
new file mode 100644
index 000000000000..0b57f5b2e038
--- /dev/null
+++ b/nixpkgs/nixos/tests/neo4j.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix {
+  name = "neo4j";
+
+  nodes = {
+    server =
+      { ... }:
+
+      {
+        virtualisation.memorySize = 4096;
+        virtualisation.diskSize = 1024;
+
+        services.neo4j.enable = true;
+        # require tls certs to be available
+        services.neo4j.https.enable = false;
+        services.neo4j.bolt.enable = false;
+      };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("neo4j.service")
+    server.wait_for_open_port(7474)
+    server.succeed("curl -f http://localhost:7474/")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/netbird.nix b/nixpkgs/nixos/tests/netbird.nix
new file mode 100644
index 000000000000..7342e8d04a39
--- /dev/null
+++ b/nixpkgs/nixos/tests/netbird.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "netbird";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ misuzu ];
+  };
+
+  nodes = {
+    node = { ... }: {
+      services.netbird.enable = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+    node.wait_for_unit("netbird-wt0.service")
+    node.wait_for_file("/var/run/netbird/sock")
+    node.succeed("netbird status | grep -q 'Daemon status: NeedsLogin'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/netdata.nix b/nixpkgs/nixos/tests/netdata.nix
new file mode 100644
index 000000000000..e3438f63404e
--- /dev/null
+++ b/nixpkgs/nixos/tests/netdata.nix
@@ -0,0 +1,41 @@
+# This test runs netdata and checks for data via apps.plugin
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "netdata";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ cransom raitobezarius ];
+  };
+
+  nodes = {
+    netdata =
+      { pkgs, ... }:
+        {
+          environment.systemPackages = with pkgs; [ curl jq netdata ];
+          services.netdata.enable = true;
+        };
+    };
+
+  testScript = ''
+    start_all()
+
+    netdata.wait_for_unit("netdata.service")
+
+    # wait for the service to listen before sending a request
+    netdata.wait_for_open_port(19999)
+
+    # check if the netdata main page loads.
+    netdata.succeed("curl --fail http://localhost:19999/")
+    netdata.succeed("sleep 4")
+
+    # check if netdata can read disk ops for root owned processes.
+    # if > 0, successful. verifies both netdata working and
+    # apps.plugin has elevated capabilities.
+    url = "http://localhost:19999/api/v1/data\?chart=user.root_disk_physical_io"
+    filter = '[.data[range(10)][2]] | add | . < 0'
+    cmd = f"curl -s {url} | jq -e '{filter}'"
+    netdata.wait_until_succeeds(cmd)
+
+    # check if the control socket is available
+    netdata.succeed("sudo netdatacli ping")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/networking-proxy.nix b/nixpkgs/nixos/tests/networking-proxy.nix
new file mode 100644
index 000000000000..330bac2588a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/networking-proxy.nix
@@ -0,0 +1,134 @@
+# Test whether `networking.proxy' work as expected.
+
+# TODO: use a real proxy node and put this test into networking.nix
+# TODO: test whether nix tools work as expected behind a proxy
+
+let default-config = {
+        imports = [ ./common/user-account.nix ];
+
+        services.xserver.enable = false;
+
+      };
+in import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "networking-proxy";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [  ];
+  };
+
+  nodes = {
+    # no proxy
+    machine =
+      { ... }:
+
+      default-config;
+
+    # proxy default
+    machine2 =
+      { ... }:
+
+      default-config // {
+        networking.proxy.default = "http://user:pass@host:port";
+      };
+
+    # specific proxy options
+    machine3 =
+      { ... }:
+
+      default-config //
+      {
+        networking.proxy = {
+          # useless because overridden by the next options
+          default = "http://user:pass@host:port";
+          # advanced proxy setup
+          httpProxy = "123-http://user:pass@http-host:port";
+          httpsProxy = "456-http://user:pass@https-host:port";
+          rsyncProxy = "789-http://user:pass@rsync-host:port";
+          ftpProxy = "101112-http://user:pass@ftp-host:port";
+          noProxy = "131415-127.0.0.1,localhost,.localdomain";
+        };
+      };
+
+    # mix default + proxy options
+    machine4 =
+      { ... }:
+
+      default-config // {
+        networking.proxy = {
+          # open for all *_proxy env var
+          default = "000-http://user:pass@default-host:port";
+          # except for those 2
+          rsyncProxy = "123-http://user:pass@http-host:port";
+          noProxy = "131415-127.0.0.1,localhost,.localdomain";
+        };
+      };
+    };
+
+  testScript =
+    ''
+      from typing import Dict, Optional
+
+
+      def get_machine_env(machine: Machine, user: Optional[str] = None) -> Dict[str, str]:
+          """
+          Gets the environment from a given machine, and returns it as a
+          dictionary in the form:
+              {"lowercase_var_name": "value"}
+
+          Duplicate environment variables with the same name
+          (e.g. "foo" and "FOO") are handled in an undefined manner.
+          """
+          if user is not None:
+              env = machine.succeed("su - {} -c 'env -0'".format(user))
+          else:
+              env = machine.succeed("env -0")
+          ret = {}
+          for line in env.split("\0"):
+              if "=" not in line:
+                  continue
+
+              key, val = line.split("=", 1)
+              ret[key.lower()] = val
+          return ret
+
+
+      start_all()
+
+      with subtest("no proxy"):
+          assert "proxy" not in machine.succeed("env").lower()
+          assert "proxy" not in machine.succeed("su - alice -c env").lower()
+
+      with subtest("default proxy"):
+          assert "proxy" in machine2.succeed("env").lower()
+          assert "proxy" in machine2.succeed("su - alice -c env").lower()
+
+      with subtest("explicitly-set proxy"):
+          env = get_machine_env(machine3)
+          assert "123" in env["http_proxy"]
+          assert "456" in env["https_proxy"]
+          assert "789" in env["rsync_proxy"]
+          assert "101112" in env["ftp_proxy"]
+          assert "131415" in env["no_proxy"]
+
+          env = get_machine_env(machine3, "alice")
+          assert "123" in env["http_proxy"]
+          assert "456" in env["https_proxy"]
+          assert "789" in env["rsync_proxy"]
+          assert "101112" in env["ftp_proxy"]
+          assert "131415" in env["no_proxy"]
+
+      with subtest("default proxy + some other specifics"):
+          env = get_machine_env(machine4)
+          assert "000" in env["http_proxy"]
+          assert "000" in env["https_proxy"]
+          assert "123" in env["rsync_proxy"]
+          assert "000" in env["ftp_proxy"]
+          assert "131415" in env["no_proxy"]
+
+          env = get_machine_env(machine4, "alice")
+          assert "000" in env["http_proxy"]
+          assert "000" in env["https_proxy"]
+          assert "123" in env["rsync_proxy"]
+          assert "000" in env["ftp_proxy"]
+          assert "131415" in env["no_proxy"]
+    '';
+})
diff --git a/nixpkgs/nixos/tests/networking.nix b/nixpkgs/nixos/tests/networking.nix
new file mode 100644
index 000000000000..6bd89902eedb
--- /dev/null
+++ b/nixpkgs/nixos/tests/networking.nix
@@ -0,0 +1,1068 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+# bool: whether to use networkd in the tests
+, networkd }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
+
+  router = { config, pkgs, lib, ... }:
+    with pkgs.lib;
+    let
+      vlanIfs = range 1 (length config.virtualisation.vlans);
+    in {
+      environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
+      virtualisation.vlans = [ 1 2 3 ];
+      boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+      networking = {
+        useDHCP = false;
+        useNetworkd = networkd;
+        firewall.checkReversePath = true;
+        firewall.allowedUDPPorts = [ 547 ];
+        interfaces = mkOverride 0 (listToAttrs (forEach vlanIfs (n:
+          nameValuePair "eth${toString n}" {
+            ipv4.addresses = [ { address = "192.168.${toString n}.1"; prefixLength = 24; } ];
+            ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ];
+          })));
+      };
+      services.kea = {
+        dhcp4 = {
+          enable = true;
+          settings = {
+            interfaces-config = {
+              interfaces = map (n: "eth${toString n}") vlanIfs;
+              dhcp-socket-type = "raw";
+              service-sockets-require-all = true;
+              service-sockets-max-retries = 5;
+              service-sockets-retry-wait-time = 2500;
+            };
+            subnet4 = map (n: {
+              id = n;
+              subnet = "192.168.${toString n}.0/24";
+              pools = [{ pool = "192.168.${toString n}.3 - 192.168.${toString n}.254"; }];
+              option-data = [{ name = "routers"; data = "192.168.${toString n}.1"; }];
+
+              reservations = [{
+                hw-address = qemu-common.qemuNicMac n 1;
+                hostname = "client${toString n}";
+                ip-address = "192.168.${toString n}.2";
+              }];
+            }) vlanIfs;
+          };
+        };
+        dhcp6 = {
+          enable = true;
+          settings = {
+            interfaces-config = {
+              interfaces = map (n: "eth${toString n}") vlanIfs;
+              service-sockets-require-all = true;
+              service-sockets-max-retries = 5;
+              service-sockets-retry-wait-time = 2500;
+            };
+
+            subnet6 = map (n: {
+              id = n;
+              subnet = "fd00:1234:5678:${toString n}::/64";
+              interface = "eth${toString n}";
+              pools = [{ pool = "fd00:1234:5678:${toString n}::2-fd00:1234:5678:${toString n}::2"; }];
+            }) vlanIfs;
+          };
+        };
+      };
+      services.radvd = {
+        enable = true;
+        config = flip concatMapStrings vlanIfs (n: ''
+          interface eth${toString n} {
+            AdvSendAdvert on;
+            AdvManagedFlag on;
+            AdvOtherConfigFlag on;
+
+            prefix fd00:1234:5678:${toString n}::/64 {
+              AdvAutonomous off;
+            };
+          };
+        '');
+      };
+    };
+
+  testCases = {
+    loopback = {
+      name = "Loopback";
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        networking.useDHCP = false;
+        networking.useNetworkd = networkd;
+      };
+      testScript = ''
+        start_all()
+        client.wait_for_unit("network.target")
+        loopback_addresses = client.succeed("ip addr show lo")
+        assert "inet 127.0.0.1/8" in loopback_addresses
+        assert "inet6 ::1/128" in loopback_addresses
+      '';
+    };
+    static = {
+      name = "Static";
+      nodes.router = router;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          defaultGateway = { address = "192.168.1.1"; interface = "enp1s0"; };
+          defaultGateway6 = { address = "fd00:1234:5678:1::1"; interface = "enp1s0"; };
+          interfaces.enp1s0.ipv4.addresses = [
+            { address = "192.168.1.2"; prefixLength = 24; }
+            { address = "192.168.1.3"; prefixLength = 32; }
+            { address = "192.168.1.10"; prefixLength = 32; }
+          ];
+          interfaces.enp2s0.ipv4.addresses = [
+            { address = "192.168.2.2"; prefixLength = 24; }
+          ];
+        };
+      };
+      testScript = { ... }:
+        ''
+          start_all()
+
+          client.wait_for_unit("network.target")
+          router.systemctl("start network-online.target")
+          router.wait_for_unit("network-online.target")
+
+          with subtest("Make sure DHCP server is not started"):
+              client.fail("systemctl status kea-dhcp4-server.service")
+              client.fail("systemctl status kea-dhcp6-server.service")
+
+          with subtest("Test vlan 1"):
+              client.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client.wait_until_succeeds("ping -c 1 192.168.1.3")
+              client.wait_until_succeeds("ping -c 1 192.168.1.10")
+
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+              router.wait_until_succeeds("ping -c 1 192.168.1.3")
+              router.wait_until_succeeds("ping -c 1 192.168.1.10")
+
+          with subtest("Test vlan 2"):
+              client.wait_until_succeeds("ping -c 1 192.168.2.1")
+              client.wait_until_succeeds("ping -c 1 192.168.2.2")
+
+              router.wait_until_succeeds("ping -c 1 192.168.2.1")
+              router.wait_until_succeeds("ping -c 1 192.168.2.2")
+
+          with subtest("Test default gateway"):
+              router.wait_until_succeeds("ping -c 1 192.168.3.1")
+              client.wait_until_succeeds("ping -c 1 192.168.3.1")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
+        '';
+    };
+    routeType = {
+      name = "RouteType";
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        networking = {
+          useDHCP = false;
+          useNetworkd = networkd;
+          interfaces.eth1.ipv4.routes = [{
+            address = "192.168.1.127";
+            prefixLength = 32;
+            type = "local";
+          }];
+        };
+      };
+      testScript = ''
+        start_all()
+        client.wait_for_unit("network.target")
+        client.succeed("ip -4 route list table local | grep 'local 192.168.1.127'")
+      '';
+    };
+    dhcpDefault = {
+      name = "useDHCP-by-default";
+      nodes.router = router;
+      nodes.client = { lib, ... }: {
+        # Disable test driver default config
+        networking.interfaces = lib.mkForce {
+          # Make sure DHCP defaults correctly even when some unrelated config
+          # is set on the interface (nothing, in this case).
+          enp1s0 = {};
+        };
+        networking.useNetworkd = networkd;
+        virtualisation.interfaces.enp1s0.vlan = 1;
+      };
+      testScript = ''
+        start_all()
+        client.wait_for_unit("multi-user.target")
+        client.wait_until_succeeds("ip addr show dev enp1s0 | grep '192.168.1'")
+        client.shell_interact()
+        client.succeed("ping -c 1 192.168.1.1")
+        router.succeed("ping -c 1 192.168.1.1")
+        router.succeed("ping -c 1 192.168.1.2")
+        client.succeed("ping -c 1 192.168.1.2")
+      '';
+    };
+    dhcpSimple = {
+      name = "SimpleDHCP";
+      nodes.router = router;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.enp1s0.useDHCP = true;
+          interfaces.enp2s0.useDHCP = true;
+        };
+      };
+      testScript = { ... }:
+        ''
+          start_all()
+
+          client.wait_for_unit("network.target")
+          router.systemctl("start network-online.target")
+          router.wait_for_unit("network-online.target")
+
+          with subtest("Wait until we have an ip address on each interface"):
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
+              client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'")
+              client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'")
+
+          with subtest("Test vlan 1"):
+              client.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
+
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
+
+          with subtest("Test vlan 2"):
+              client.wait_until_succeeds("ping -c 1 192.168.2.1")
+              client.wait_until_succeeds("ping -c 1 192.168.2.2")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
+
+              router.wait_until_succeeds("ping -c 1 192.168.2.1")
+              router.wait_until_succeeds("ping -c 1 192.168.2.2")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
+              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
+        '';
+    };
+    dhcpOneIf = {
+      name = "OneInterfaceDHCP";
+      nodes.router = router;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.enp1s0 = {
+            mtu = 1343;
+            useDHCP = true;
+          };
+        };
+      };
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to come up"):
+              client.wait_for_unit("network.target")
+              router.wait_for_unit("network.target")
+
+          with subtest("Wait until we have an ip address on each interface"):
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
+
+          with subtest("ensure MTU is set"):
+              assert "mtu 1343" in client.succeed("ip link show dev enp1s0")
+
+          with subtest("Test vlan 1"):
+              client.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client.wait_until_succeeds("ping -c 1 192.168.1.2")
+
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+
+          with subtest("Test vlan 2"):
+              client.wait_until_succeeds("ping -c 1 192.168.2.1")
+              client.fail("ping -c 1 192.168.2.2")
+
+              router.wait_until_succeeds("ping -c 1 192.168.2.1")
+              router.fail("ping -c 1 192.168.2.2")
+        '';
+    };
+    bond = let
+      node = address: { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          bonds.bond0 = {
+            interfaces = [ "enp1s0" "enp2s0" ];
+            driverOptions.mode = "802.3ad";
+          };
+          interfaces.bond0.ipv4.addresses = mkOverride 0
+            [ { inherit address; prefixLength = 30; } ];
+        };
+      };
+    in {
+      name = "Bond";
+      nodes.client1 = node "192.168.1.1";
+      nodes.client2 = node "192.168.1.2";
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to come up"):
+              client1.wait_for_unit("network.target")
+              client2.wait_for_unit("network.target")
+
+          with subtest("Test bonding"):
+              client1.wait_until_succeeds("ping -c 2 192.168.1.1")
+              client1.wait_until_succeeds("ping -c 2 192.168.1.2")
+
+              client2.wait_until_succeeds("ping -c 2 192.168.1.1")
+              client2.wait_until_succeeds("ping -c 2 192.168.1.2")
+
+          with subtest("Verify bonding mode"):
+              for client in client1, client2:
+                  client.succeed('grep -q "Bonding Mode: IEEE 802.3ad Dynamic link aggregation" /proc/net/bonding/bond0')
+        '';
+    };
+    bridge = let
+      node = { address, vlan }: { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = vlan;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.enp1s0.ipv4.addresses = [ { inherit address; prefixLength = 24; } ];
+        };
+      };
+    in {
+      name = "Bridge";
+      nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
+      nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
+      nodes.router = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        virtualisation.interfaces.enp2s0.vlan = 2;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          bridges.bridge.interfaces = [ "enp1s0" "enp2s0" ];
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.bridge.ipv4.addresses = mkOverride 0
+            [ { address = "192.168.1.1"; prefixLength = 24; } ];
+        };
+      };
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to come up"):
+              for machine in client1, client2, router:
+                  machine.wait_for_unit("network.target")
+
+          with subtest("Test bridging"):
+              client1.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client1.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client1.wait_until_succeeds("ping -c 1 192.168.1.3")
+
+              client2.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client2.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client2.wait_until_succeeds("ping -c 1 192.168.1.3")
+
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+              router.wait_until_succeeds("ping -c 1 192.168.1.3")
+        '';
+    };
+    macvlan = {
+      name = "MACVLAN";
+      nodes.router = router;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          firewall.logReversePathDrops = true; # to debug firewall rules
+          # reverse path filtering rules for the macvlan interface seem
+          # to be incorrect, causing the test to fail. Disable temporarily.
+          firewall.checkReversePath = false;
+          macvlans.macvlan.interface = "enp1s0";
+          interfaces.enp1s0.useDHCP = true;
+          interfaces.macvlan.useDHCP = true;
+        };
+      };
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to come up"):
+              client.wait_for_unit("network.target")
+              router.wait_for_unit("network.target")
+
+          with subtest("Wait until we have an ip address on each interface"):
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
+              client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'")
+
+          with subtest("Print lots of diagnostic information"):
+              router.log("**********************************************")
+              router.succeed("ip addr >&2")
+              router.succeed("ip route >&2")
+              router.execute("iptables-save >&2")
+              client.log("==============================================")
+              client.succeed("ip addr >&2")
+              client.succeed("ip route >&2")
+              client.execute("iptables-save >&2")
+              client.log("##############################################")
+
+          with subtest("Test macvlan creates routable ips"):
+              client.wait_until_succeeds("ping -c 1 192.168.1.1")
+              client.wait_until_succeeds("ping -c 1 192.168.1.2")
+              client.wait_until_succeeds("ping -c 1 192.168.1.3")
+
+              router.wait_until_succeeds("ping -c 1 192.168.1.1")
+              router.wait_until_succeeds("ping -c 1 192.168.1.2")
+              router.wait_until_succeeds("ping -c 1 192.168.1.3")
+        '';
+    };
+    fou = {
+      name = "foo-over-udp";
+      nodes.machine = { ... }: {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.enp1s0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          fooOverUDP = {
+            fou1 = { port = 9001; };
+            fou2 = { port = 9002; protocol = 41; };
+            fou3 = mkIf (!networkd)
+              { port = 9003; local.address = "192.168.1.1"; };
+            fou4 = mkIf (!networkd)
+              { port = 9004; local = { address = "192.168.1.1"; dev = "enp1s0"; }; };
+          };
+        };
+        systemd.services = {
+          fou3-fou-encap.after = optional (!networkd) "network-addresses-enp1s0.service";
+        };
+      };
+      testScript = { ... }:
+        ''
+          import json
+
+          machine.wait_for_unit("network.target")
+          fous = json.loads(machine.succeed("ip -json fou show"))
+          assert {"port": 9001, "gue": None, "family": "inet"} in fous, "fou1 exists"
+          assert {"port": 9002, "ipproto": 41, "family": "inet"} in fous, "fou2 exists"
+        '' + optionalString (!networkd) ''
+          assert {
+              "port": 9003,
+              "gue": None,
+              "family": "inet",
+              "local": "192.168.1.1",
+          } in fous, "fou3 exists"
+          assert {
+              "port": 9004,
+              "gue": None,
+              "family": "inet",
+              "local": "192.168.1.1",
+              "dev": "enp1s0",
+          } in fous, "fou4 exists"
+        '';
+    };
+    sit = let
+      node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          sits.sit = {
+            inherit remote;
+            local = address4;
+            dev = "enp1s0";
+          };
+          interfaces.enp1s0.ipv4.addresses = mkOverride 0
+            [ { address = address4; prefixLength = 24; } ];
+          interfaces.sit.ipv6.addresses = mkOverride 0
+            [ { address = address6; prefixLength = 64; } ];
+        };
+      };
+    in {
+      name = "Sit";
+      # note on firewalling: the two nodes are explicitly asymmetric.
+      # client1 sends SIT packets in UDP, but accepts only proto-41 incoming.
+      # client2 does the reverse, sending in proto-41 and accepting only UDP incoming.
+      # that way we'll notice when either SIT itself or FOU breaks.
+      nodes.client1 = args@{ pkgs, ... }:
+        mkMerge [
+          (node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; } args)
+          {
+            networking = {
+              firewall.extraCommands = "iptables -A INPUT -p 41 -j ACCEPT";
+              sits.sit.encapsulation = { type = "fou"; port = 9001; };
+            };
+          }
+        ];
+      nodes.client2 = args@{ pkgs, ... }:
+        mkMerge [
+          (node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; } args)
+          {
+            networking = {
+              firewall.allowedUDPPorts = [ 9001 ];
+              fooOverUDP.fou1 = { port = 9001; protocol = 41; };
+            };
+          }
+        ];
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to be configured"):
+              client1.wait_for_unit("network.target")
+              client2.wait_for_unit("network.target")
+
+              # Print diagnostic information
+              client1.succeed("ip addr >&2")
+              client2.succeed("ip addr >&2")
+
+          with subtest("Test ipv6"):
+              client1.wait_until_succeeds("ping -c 1 fc00::1")
+              client1.wait_until_succeeds("ping -c 1 fc00::2")
+
+              client2.wait_until_succeeds("ping -c 1 fc00::1")
+              client2.wait_until_succeeds("ping -c 1 fc00::2")
+        '';
+    };
+    gre = let
+      node = { pkgs, ... }: with pkgs.lib; {
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          firewall.extraCommands = "ip6tables -A nixos-fw -p gre -j nixos-fw-accept";
+        };
+      };
+    in {
+      name = "GRE";
+      nodes.client1 = args@{ pkgs, ... }:
+        mkMerge [
+          (node args)
+          {
+            virtualisation.vlans = [ 1 2 4 ];
+            networking = {
+              greTunnels = {
+                greTunnel = {
+                  local = "192.168.2.1";
+                  remote = "192.168.2.2";
+                  dev = "eth2";
+                  ttl = 225;
+                  type = "tap";
+                };
+                gre6Tunnel = {
+                  local = "fd00:1234:5678:4::1";
+                  remote = "fd00:1234:5678:4::2";
+                  dev = "eth3";
+                  ttl = 255;
+                  type = "tun6";
+                };
+              };
+              bridges.bridge.interfaces = [ "greTunnel" "eth1" ];
+              interfaces.eth1.ipv4.addresses = mkOverride 0 [];
+              interfaces.bridge.ipv4.addresses = mkOverride 0 [
+                { address = "192.168.1.1"; prefixLength = 24; }
+              ];
+              interfaces.eth3.ipv6.addresses = [
+                { address = "fd00:1234:5678:4::1"; prefixLength = 64; }
+              ];
+              interfaces.gre6Tunnel.ipv6.addresses = mkOverride 0 [
+                { address = "fc00::1"; prefixLength = 64; }
+              ];
+            };
+          }
+        ];
+      nodes.client2 = args@{ pkgs, ... }:
+        mkMerge [
+          (node args)
+          {
+            virtualisation.vlans = [ 2 3 4 ];
+            networking = {
+              greTunnels = {
+                greTunnel = {
+                  local = "192.168.2.2";
+                  remote = "192.168.2.1";
+                  dev = "eth1";
+                  ttl = 225;
+                  type = "tap";
+                };
+                gre6Tunnel = {
+                  local = "fd00:1234:5678:4::2";
+                  remote = "fd00:1234:5678:4::1";
+                  dev = "eth3";
+                  ttl = 255;
+                  type = "tun6";
+                };
+              };
+              bridges.bridge.interfaces = [ "greTunnel" "eth2" ];
+              interfaces.eth2.ipv4.addresses = mkOverride 0 [];
+              interfaces.bridge.ipv4.addresses = mkOverride 0 [
+                { address = "192.168.1.2"; prefixLength = 24; }
+              ];
+              interfaces.eth3.ipv6.addresses = [
+                { address = "fd00:1234:5678:4::2"; prefixLength = 64; }
+              ];
+              interfaces.gre6Tunnel.ipv6.addresses = mkOverride 0 [
+                { address = "fc00::2"; prefixLength = 64; }
+              ];
+            };
+          }
+        ];
+      testScript = { ... }:
+        ''
+          import json
+          start_all()
+
+          with subtest("Wait for networking to be configured"):
+              client1.wait_for_unit("network.target")
+              client2.wait_for_unit("network.target")
+
+              # Print diagnostic information
+              client1.succeed("ip addr >&2")
+              client2.succeed("ip addr >&2")
+
+          with subtest("Test GRE tunnel bridge over VLAN"):
+              client1.wait_until_succeeds("ping -c 1 192.168.1.2")
+
+              client2.wait_until_succeeds("ping -c 1 192.168.1.1")
+
+              client1.wait_until_succeeds("ping -c 1 fc00::2")
+
+              client2.wait_until_succeeds("ping -c 1 fc00::1")
+
+          with subtest("Test GRE tunnel TTL"):
+              links = json.loads(client1.succeed("ip -details -json link show greTunnel"))
+              assert links[0]['linkinfo']['info_data']['ttl'] == 225, "ttl not set for greTunnel"
+
+              links = json.loads(client2.succeed("ip -details -json link show gre6Tunnel"))
+              assert links[0]['linkinfo']['info_data']['ttl'] == 255, "ttl not set for gre6Tunnel"
+        '';
+    };
+    vlan = let
+      node = address: { pkgs, ... }: with pkgs.lib; {
+        #virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          vlans.vlan = {
+            id = 1;
+            interface = "eth0";
+          };
+          interfaces.eth0.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
+          interfaces.vlan.ipv4.addresses = mkOverride 0
+            [ { inherit address; prefixLength = 24; } ];
+        };
+      };
+    in {
+      name = "vlan";
+      nodes.client1 = node "192.168.1.1";
+      nodes.client2 = node "192.168.1.2";
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to be configured"):
+              client1.wait_for_unit("network.target")
+              client2.wait_for_unit("network.target")
+
+          with subtest("Test vlan is setup"):
+              client1.succeed("ip addr show dev vlan >&2")
+              client2.succeed("ip addr show dev vlan >&2")
+        '';
+    };
+    vlan-ping = let
+        baseIP = number: "10.10.10.${number}";
+        vlanIP = number: "10.1.1.${number}";
+        baseInterface = "enp1s0";
+        vlanInterface = "vlan42";
+        node = number: {pkgs, ... }: with pkgs.lib; {
+          virtualisation.interfaces.enp1s0.vlan = 1;
+          networking = {
+            #useNetworkd = networkd;
+            useDHCP = false;
+            vlans.${vlanInterface} = { id = 42; interface = baseInterface; };
+            interfaces.${baseInterface}.ipv4.addresses = mkOverride 0 [{ address = baseIP number; prefixLength = 24; }];
+            interfaces.${vlanInterface}.ipv4.addresses = mkOverride 0 [{ address = vlanIP number; prefixLength = 24; }];
+          };
+        };
+
+        serverNodeNum = "1";
+        clientNodeNum = "2";
+
+    in {
+      name = "vlan-ping";
+      nodes.server = node serverNodeNum;
+      nodes.client = node clientNodeNum;
+      testScript = { ... }:
+        ''
+          start_all()
+
+          with subtest("Wait for networking to be configured"):
+              server.wait_for_unit("network.target")
+              client.wait_for_unit("network.target")
+
+          with subtest("Test ping on base interface in setup"):
+              client.succeed("ping -I ${baseInterface} -c 1 ${baseIP serverNodeNum}")
+              server.succeed("ping -I ${baseInterface} -c 1 ${baseIP clientNodeNum}")
+
+          with subtest("Test ping on vlan subinterface in setup"):
+              client.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP serverNodeNum}")
+              server.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP clientNodeNum}")
+        '';
+    };
+    virtual = {
+      name = "Virtual";
+      nodes.machine = {
+        networking.useNetworkd = networkd;
+        networking.useDHCP = false;
+        networking.interfaces.tap0 = {
+          ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2096::"; prefixLength = 64; } ];
+          virtual = true;
+          mtu = 1342;
+          macAddress = "02:de:ad:be:ef:01";
+        };
+        networking.interfaces.tun0 = {
+          ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
+          virtual = true;
+          mtu = 1343;
+        };
+      };
+
+      testScript = ''
+        targetList = """
+        tap0: tap persist user 0
+        tun0: tun persist user 0
+        """.strip()
+
+        with subtest("Wait for networking to come up"):
+            machine.start()
+            machine.wait_for_unit("network.target")
+
+        with subtest("Test interfaces set up"):
+            list = machine.succeed("ip tuntap list | sort").strip()
+            assert (
+                list == targetList
+            ), """
+            The list of virtual interfaces does not match the expected one:
+            Result:
+              {}
+            Expected:
+              {}
+            """.format(
+                list, targetList
+            )
+        with subtest("Test MTU and MAC Address are configured"):
+            machine.wait_until_succeeds("ip link show dev tap0 | grep 'mtu 1342'")
+            machine.wait_until_succeeds("ip link show dev tun0 | grep 'mtu 1343'")
+            assert "02:de:ad:be:ef:01" in machine.succeed("ip link show dev tap0")
+      '' # network-addresses-* only exist in scripted networking
+      + optionalString (!networkd) ''
+        with subtest("Test interfaces clean up"):
+            machine.succeed("systemctl stop network-addresses-tap0")
+            machine.sleep(10)
+            machine.succeed("systemctl stop network-addresses-tun0")
+            machine.sleep(10)
+            residue = machine.succeed("ip tuntap list")
+            assert (
+                residue == ""
+            ), "Some virtual interface has not been properly cleaned:\n{}".format(residue)
+      '';
+    };
+    privacy = {
+      name = "Privacy";
+      nodes.router = { ... }: {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.enp1s0.ipv6.addresses = singleton {
+            address = "fd00:1234:5678:1::1";
+            prefixLength = 64;
+          };
+        };
+        services.radvd = {
+          enable = true;
+          config = ''
+            interface enp1s0 {
+              AdvSendAdvert on;
+              AdvManagedFlag on;
+              AdvOtherConfigFlag on;
+
+              prefix fd00:1234:5678:1::/64 {
+                AdvAutonomous on;
+                AdvOnLink on;
+              };
+            };
+          '';
+        };
+      };
+      nodes.client_with_privacy = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.enp1s0 = {
+            tempAddress = "default";
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
+            useDHCP = true;
+          };
+        };
+      };
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.interfaces.enp1s0.vlan = 1;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+          interfaces.enp1s0 = {
+            tempAddress = "enabled";
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
+            useDHCP = true;
+          };
+        };
+      };
+      testScript = { ... }:
+        ''
+          start_all()
+
+          client.wait_for_unit("network.target")
+          client_with_privacy.wait_for_unit("network.target")
+          router.systemctl("start network-online.target")
+          router.wait_for_unit("network-online.target")
+
+          with subtest("Wait until we have an ip address"):
+              client_with_privacy.wait_until_succeeds(
+                  "ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'"
+              )
+              client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
+
+          with subtest("Test vlan 1"):
+              client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
+
+          with subtest("Test address used is temporary"):
+              client_with_privacy.wait_until_succeeds(
+                  "! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
+              )
+
+          with subtest("Test address used is EUI-64"):
+              client.wait_until_succeeds(
+                  "ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
+              )
+        '';
+    };
+    routes = {
+      name = "routes";
+      nodes.machine = {
+        networking.useNetworkd = networkd;
+        networking.useDHCP = false;
+        networking.interfaces.eth0 = {
+          ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
+          ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
+          ipv6.routes = [
+            { address = "fdfd:b3f0::"; prefixLength = 48; }
+            { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
+          ];
+          ipv4.routes = [
+            { address = "10.0.0.0"; prefixLength = 16; options = {
+              mtu = "1500";
+              # Explicitly set scope because iproute and systemd-networkd
+              # disagree on what the scope should be
+              # if the type is the default "unicast"
+              scope = "link";
+            }; }
+            { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
+          ];
+        };
+        virtualisation.vlans = [ ];
+      };
+
+      testScript = ''
+        targetIPv4Table = [
+            "10.0.0.0/16 proto static scope link mtu 1500",
+            "192.168.1.0/24 proto kernel scope link src 192.168.1.2",
+            "192.168.2.0/24 via 192.168.1.1 proto static",
+        ]
+
+        targetIPv6Table = [
+            "2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium",
+            "2001:1470:fffd:2098::/64 via fdfd:b3f0::1 proto static metric 1024 pref medium",
+            "fdfd:b3f0::/48 proto static metric 1024 pref medium",
+        ]
+
+        machine.start()
+        machine.wait_for_unit("network.target")
+
+        with subtest("test routing tables"):
+            ipv4Table = machine.succeed("ip -4 route list dev eth0 | head -n3").strip()
+            ipv6Table = machine.succeed("ip -6 route list dev eth0 | head -n3").strip()
+            assert [
+                l.strip() for l in ipv4Table.splitlines()
+            ] == targetIPv4Table, """
+              The IPv4 routing table does not match the expected one:
+                Result:
+                  {}
+                Expected:
+                  {}
+              """.format(
+                ipv4Table, targetIPv4Table
+            )
+            assert [
+                l.strip() for l in ipv6Table.splitlines()
+            ] == targetIPv6Table, """
+              The IPv6 routing table does not match the expected one:
+                Result:
+                  {}
+                Expected:
+                  {}
+              """.format(
+                ipv6Table, targetIPv6Table
+            )
+
+      '' + optionalString (!networkd) ''
+        with subtest("test clean-up of the tables"):
+            machine.succeed("systemctl stop network-addresses-eth0")
+            ipv4Residue = machine.succeed("ip -4 route list dev eth0 | head -n-3").strip()
+            ipv6Residue = machine.succeed("ip -6 route list dev eth0 | head -n-3").strip()
+            assert (
+                ipv4Residue == ""
+            ), "The IPv4 routing table has not been properly cleaned:\n{}".format(ipv4Residue)
+            assert (
+                ipv6Residue == ""
+            ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue)
+      '';
+    };
+    rename = if networkd then {
+      name = "RenameInterface";
+      nodes.machine = { pkgs, ... }: {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+        };
+        systemd.network.links."10-custom_name" = {
+          matchConfig.MACAddress = "52:54:00:12:01:01";
+          linkConfig.Name = "custom_name";
+        };
+      };
+      testScript = ''
+        machine.succeed("udevadm settle")
+        print(machine.succeed("ip link show dev custom_name"))
+      '';
+    } else {
+      name = "RenameInterface";
+      nodes = { };
+      testScript = "";
+    };
+    # even with disabled networkd, systemd.network.links should work
+    # (as it's handled by udev, not networkd)
+    link = {
+      name = "Link";
+      nodes.client = { pkgs, ... }: {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+        };
+        systemd.network.links."50-foo" = {
+          matchConfig = {
+            Name = "foo";
+            Driver = "dummy";
+          };
+          linkConfig.MTUBytes = "1442";
+        };
+      };
+      testScript = ''
+        print(client.succeed("ip l add name foo type dummy"))
+        print(client.succeed("stat /etc/systemd/network/50-foo.link"))
+        client.succeed("udevadm settle")
+        assert "mtu 1442" in client.succeed("ip l show dev foo")
+      '';
+    };
+    wlanInterface = let
+      testMac = "06:00:00:00:02:00";
+    in {
+      name = "WlanInterface";
+      nodes.machine = { pkgs, ... }: {
+        boot.kernelModules = [ "mac80211_hwsim" ];
+        networking.wlanInterfaces = {
+          wlan0 = { device = "wlan0"; };
+          wap0 = { device = "wlan0"; mac = testMac; };
+        };
+      };
+      testScript = ''
+        machine.start()
+        machine.wait_for_unit("network.target")
+        machine.wait_until_succeeds("ip address show wap0 | grep -q ${testMac}")
+        machine.fail("ip address show wlan0 | grep -q ${testMac}")
+      '';
+    };
+    naughtyInterfaceNames = let
+      ifnames = [
+        # flags of ip-address
+        "home" "temporary" "optimistic"
+        "bridge_slave" "flush"
+        # flags of ip-route
+        "up" "type" "nomaster" "address"
+        # other
+        "very_loong_name" "lowerUpper" "-"
+      ];
+    in {
+      name = "naughtyInterfaceNames";
+      nodes.machine = { pkgs, ... }: {
+        networking.useNetworkd = networkd;
+        networking.bridges = listToAttrs
+          (flip map ifnames
+             (name: { inherit name; value.interfaces = []; }));
+      };
+      testScript = ''
+        machine.start()
+        machine.wait_for_unit("network.target")
+        for ifname in ${builtins.toJSON ifnames}:
+            machine.wait_until_succeeds(f"ip link show dev '{ifname}' | grep -q '{ifname}'")
+      '';
+    };
+    caseSensitiveRenaming = {
+      name = "CaseSensitiveRenaming";
+      nodes.machine = { pkgs, ... }: {
+        virtualisation.interfaces.enCustom.vlan = 11;
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = false;
+        };
+      };
+      testScript = ''
+        machine.succeed("udevadm settle")
+        print(machine.succeed("ip link show dev enCustom"))
+        machine.wait_until_succeeds("ip link show dev enCustom | grep -q 52:54:00:12:0b:01")
+      '';
+    };
+  };
+
+in mapAttrs (const (attrs: makeTest (attrs // {
+  name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
+}))) testCases
diff --git a/nixpkgs/nixos/tests/nextcloud/basic.nix b/nixpkgs/nixos/tests/nextcloud/basic.nix
new file mode 100644
index 000000000000..428fe0aa10db
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/basic.nix
@@ -0,0 +1,120 @@
+args@{ pkgs, nextcloudVersion ? 22, ... }:
+
+(import ../make-test-python.nix ({ pkgs, ...}: let
+  adminpass = "notproduction";
+  adminuser = "root";
+in {
+  name = "nextcloud-basic";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ globin eqyiel ];
+  };
+
+  nodes = rec {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {
+      services.davfs2.enable = true;
+      systemd.tmpfiles.settings.nextcloud = {
+        "/tmp/davfs2-secrets"."f+" = {
+          mode = "0600";
+          argument = "http://nextcloud/remote.php/dav/files/${adminuser} ${adminuser} ${adminpass}";
+        };
+      };
+      virtualisation.fileSystems = {
+        "/mnt/dav" = {
+          device = "http://nextcloud/remote.php/dav/files/${adminuser}";
+          fsType = "davfs";
+          options = let
+            davfs2Conf = (pkgs.writeText "davfs2.conf" "secrets /tmp/davfs2-secrets");
+          in [ "conf=${davfs2Conf}" "x-systemd.automount" "noauto"];
+        };
+      };
+    };
+
+    nextcloud = { config, pkgs, ... }: let
+      cfg = config;
+    in {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      systemd.tmpfiles.rules = [
+        "d /var/lib/nextcloud-data 0750 nextcloud nginx - -"
+      ];
+
+      services.nextcloud = {
+        enable = true;
+        datadir = "/var/lib/nextcloud-data";
+        hostName = "nextcloud";
+        database.createLocally = true;
+        config = {
+          # Don't inherit adminuser since "root" is supposed to be the default
+          adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home!
+          dbtableprefix = "nixos_";
+        };
+        package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
+        autoUpdateApps = {
+          enable = true;
+          startAt = "20:00";
+        };
+        phpExtraExtensions = all: [ all.bz2 ];
+      };
+
+      environment.systemPackages = [ cfg.services.nextcloud.occ ];
+    };
+
+    nextcloudWithoutMagick = args@{ config, pkgs, lib, ... }:
+      lib.mkMerge
+      [ (nextcloud args)
+        { services.nextcloud.enableImagemagick = false; } ];
+  };
+
+  testScript = { nodes, ... }: let
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.runtimeShell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.runtimeShell}
+      echo 'hi' | ${withRcloneEnv} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.runtimeShell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+
+    findInClosure = what: drv: pkgs.runCommand "find-in-closure" { exportReferencesGraph = [ "graph" drv ]; inherit what; } ''
+      test -e graph
+      grep "$what" graph >$out || true
+    '';
+    nextcloudUsesImagick = findInClosure "imagick" nodes.nextcloud.system.build.vm;
+    nextcloudWithoutDoesntUseIt = findInClosure "imagick" nodes.nextcloudWithoutMagick.system.build.vm;
+  in ''
+    assert open("${nextcloudUsesImagick}").read() != ""
+    assert open("${nextcloudWithoutDoesntUseIt}").read() == ""
+
+    nextcloud.start()
+    client.start()
+    nextcloud.wait_for_unit("multi-user.target")
+    # This is just to ensure the nextcloud-occ program is working
+    nextcloud.succeed("nextcloud-occ status")
+    nextcloud.succeed("curl -sSf http://nextcloud/login")
+    # Ensure that no OpenSSL 1.1 is used.
+    nextcloud.succeed(
+        "${nodes.nextcloud.services.phpfpm.pools.nextcloud.phpPackage}/bin/php -i | grep 'OpenSSL Library Version' | awk -F'=>' '{ print $2 }' | awk '{ print $2 }' | grep -v 1.1"
+    )
+    nextcloud.succeed(
+        "${withRcloneEnv} ${copySharedFile}"
+    )
+    client.wait_for_unit("multi-user.target")
+    nextcloud.succeed("test -f /var/lib/nextcloud-data/data/root/files/test-shared-file")
+    client.succeed(
+        "${withRcloneEnv} ${diffSharedFile}"
+    )
+    assert "hi" in client.succeed("cat /mnt/dav/test-shared-file")
+    nextcloud.succeed("grep -vE '^HBEGIN:oc_encryption_module' /var/lib/nextcloud-data/data/root/files/test-shared-file")
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/nextcloud/default.nix b/nixpkgs/nixos/tests/nextcloud/default.nix
new file mode 100644
index 000000000000..84ac37153727
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/default.nix
@@ -0,0 +1,25 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+
+with pkgs.lib;
+
+foldl
+  (matrix: ver: matrix // {
+    "basic${toString ver}" = import ./basic.nix { inherit system pkgs; nextcloudVersion = ver; };
+    "with-postgresql-and-redis${toString ver}" = import ./with-postgresql-and-redis.nix {
+      inherit system pkgs;
+      nextcloudVersion = ver;
+    };
+    "with-mysql-and-memcached${toString ver}" = import ./with-mysql-and-memcached.nix {
+      inherit system pkgs;
+      nextcloudVersion = ver;
+    };
+    "with-declarative-redis-and-secrets${toString ver}" = import ./with-declarative-redis-and-secrets.nix {
+      inherit system pkgs;
+      nextcloudVersion = ver;
+    };
+  })
+{ }
+  [ 26 27 28 ]
diff --git a/nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
new file mode 100644
index 000000000000..b09ee1276a13
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
@@ -0,0 +1,126 @@
+args@{ nextcloudVersion ? 27, ... }:
+(import ../make-test-python.nix ({ pkgs, ...}: let
+  adminuser = "custom_admin_username";
+  # This will be used both for redis and postgresql
+  pass = "hunter2";
+  # Don't do this at home, use a file outside of the nix store instead
+  passFile = toString (pkgs.writeText "pass-file" ''
+    ${pass}
+  '');
+in {
+  name = "nextcloud-with-declarative-redis";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eqyiel ma27 ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
+        caching = {
+          apcu = false;
+          redis = true;
+          memcached = false;
+        };
+        # This test also validates that we can use an "external" database
+        database.createLocally = false;
+        config = {
+          dbtype = "pgsql";
+          dbname = "nextcloud";
+          dbuser = adminuser;
+          dbpassFile = passFile;
+          adminuser = adminuser;
+          adminpassFile = passFile;
+        };
+        secretFile = "/etc/nextcloud-secrets.json";
+
+        settings = {
+          allow_local_remote_servers = true;
+          redis = {
+            dbindex = 0;
+            timeout = 1.5;
+            # password handled via secretfile below
+          };
+        };
+        configureRedis = true;
+      };
+
+      services.redis.servers."nextcloud" = {
+        enable = true;
+        port = 6379;
+        requirePass = "secret";
+      };
+
+      systemd.services.nextcloud-setup= {
+        requires = ["postgresql.service"];
+        after = [ "postgresql.service" ];
+      };
+
+      services.postgresql = {
+        enable = true;
+        package = pkgs.postgresql_14;
+      };
+      systemd.services.postgresql.postStart = pkgs.lib.mkAfter ''
+        password=$(cat ${passFile})
+        ${config.services.postgresql.package}/bin/psql <<EOF
+          CREATE ROLE ${adminuser} WITH LOGIN PASSWORD '$password' CREATEDB;
+          CREATE DATABASE nextcloud;
+          GRANT ALL PRIVILEGES ON DATABASE nextcloud TO ${adminuser};
+        EOF
+      '';
+
+      # This file is meant to contain secret options which should
+      # not go into the nix store. Here it is just used to set the
+      # redis password.
+      environment.etc."nextcloud-secrets.json".text = ''
+        {
+          "redis": {
+            "password": "secret"
+          }
+        }
+      '';
+    };
+  };
+
+  testScript = let
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.runtimeShell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${pass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.runtimeShell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.runtimeShell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    start_all()
+    nextcloud.wait_for_unit("multi-user.target")
+    nextcloud.succeed("curl -sSf http://nextcloud/login")
+    nextcloud.succeed(
+        "${withRcloneEnv} ${copySharedFile}"
+    )
+    client.wait_for_unit("multi-user.target")
+    client.succeed(
+        "${withRcloneEnv} ${diffSharedFile}"
+    )
+
+    # redis cache should not be empty
+    nextcloud.fail('test "[]" = "$(redis-cli --json KEYS "*")"')
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix
new file mode 100644
index 000000000000..035a7fdcb0c8
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -0,0 +1,79 @@
+args@{ pkgs, nextcloudVersion ? 22, ... }:
+
+(import ../make-test-python.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "root";
+in {
+  name = "nextcloud-with-mysql-and-memcached";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        https = true;
+        package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
+        caching = {
+          apcu = true;
+          redis = false;
+          memcached = true;
+        };
+        database.createLocally = true;
+        config = {
+          dbtype = "mysql";
+          # Don't inherit adminuser since "root" is supposed to be the default
+          adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home!
+        };
+      };
+
+      services.memcached.enable = true;
+    };
+  };
+
+  testScript = let
+    configureMemcached = pkgs.writeScript "configure-memcached" ''
+      #!${pkgs.runtimeShell}
+      nextcloud-occ config:system:set memcached_servers 0 0 --value 127.0.0.1 --type string
+      nextcloud-occ config:system:set memcached_servers 0 1 --value 11211 --type integer
+      nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\APCu' --type string
+      nextcloud-occ config:system:set memcache.distributed --value '\OC\Memcache\Memcached' --type string
+    '';
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.runtimeShell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.runtimeShell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.runtimeShell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    start_all()
+    nextcloud.wait_for_unit("multi-user.target")
+    nextcloud.succeed("${configureMemcached}")
+    nextcloud.succeed("curl -sSf http://nextcloud/login")
+    nextcloud.succeed(
+        "${withRcloneEnv} ${copySharedFile}"
+    )
+    client.wait_for_unit("multi-user.target")
+    client.succeed(
+        "${withRcloneEnv} ${diffSharedFile}"
+    )
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix
new file mode 100644
index 000000000000..06afc589403d
--- /dev/null
+++ b/nixpkgs/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -0,0 +1,98 @@
+args@{ pkgs, nextcloudVersion ? 22, ... }:
+
+(import ../make-test-python.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "custom-admin-username";
+in {
+  name = "nextcloud-with-postgresql-and-redis";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, lib, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
+        caching = {
+          apcu = false;
+          redis = true;
+          memcached = false;
+        };
+        database.createLocally = true;
+        config = {
+          dbtype = "pgsql";
+          inherit adminuser;
+          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
+            ${adminpass}
+          '');
+        };
+        notify_push = {
+          enable = true;
+          logLevel = "debug";
+        };
+        extraAppsEnable = true;
+        extraApps = {
+          inherit (pkgs."nextcloud${lib.versions.major config.services.nextcloud.package.version}Packages".apps) notify_push notes;
+        };
+        settings.trusted_proxies = [ "::1" ];
+      };
+
+      services.redis.servers."nextcloud".enable = true;
+      services.redis.servers."nextcloud".port = 6379;
+    };
+  };
+
+  testScript = let
+    configureRedis = pkgs.writeScript "configure-redis" ''
+      #!${pkgs.runtimeShell}
+      nextcloud-occ config:system:set redis 'host' --value 'localhost' --type string
+      nextcloud-occ config:system:set redis 'port' --value 6379 --type integer
+      nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\Redis' --type string
+      nextcloud-occ config:system:set memcache.locking --value '\OC\Memcache\Redis' --type string
+    '';
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.runtimeShell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.runtimeShell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.runtimeShell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    start_all()
+    nextcloud.wait_for_unit("multi-user.target")
+    nextcloud.succeed("${configureRedis}")
+    nextcloud.succeed("curl -sSf http://nextcloud/login")
+    nextcloud.succeed(
+        "${withRcloneEnv} ${copySharedFile}"
+    )
+    client.wait_for_unit("multi-user.target")
+    client.execute("${pkgs.lib.getExe pkgs.nextcloud-notify_push.passthru.test_client} http://nextcloud ${adminuser} ${adminpass} >&2 &")
+    client.succeed(
+        "${withRcloneEnv} ${diffSharedFile}"
+    )
+    nextcloud.wait_until_succeeds("journalctl -u nextcloud-notify_push | grep -q \"Sending ping to ${adminuser}\"")
+
+    # redis cache should not be empty
+    nextcloud.fail('test "[]" = "$(redis-cli --json KEYS "*")"')
+
+    nextcloud.fail("curl -f http://nextcloud/nix-apps/notes/lib/AppInfo/Application.php")
+  '';
+})) args
diff --git a/nixpkgs/nixos/tests/nexus.nix b/nixpkgs/nixos/tests/nexus.nix
new file mode 100644
index 000000000000..87bb4d2eb58a
--- /dev/null
+++ b/nixpkgs/nixos/tests/nexus.nix
@@ -0,0 +1,32 @@
+# verifies:
+#   1. nexus service starts on server
+#   2. nexus service can startup on server (creating database and all other initial stuff)
+#   3. the web application is reachable via HTTP
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "nexus";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ironpinguin ];
+  };
+
+  nodes = {
+
+    server =
+      { ... }:
+      { virtualisation.memorySize = 2047; # qemu-system-i386 has a 2047M limit
+        virtualisation.diskSize = 8192;
+
+        services.nexus.enable = true;
+      };
+
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("nexus")
+    server.wait_for_open_port(8081)
+
+    server.succeed("curl -f 127.0.0.1:8081")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nfs/default.nix b/nixpkgs/nixos/tests/nfs/default.nix
new file mode 100644
index 000000000000..6bc803c91b46
--- /dev/null
+++ b/nixpkgs/nixos/tests/nfs/default.nix
@@ -0,0 +1,9 @@
+{ version ? 4
+, system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}: {
+  simple = import ./simple.nix { inherit version system pkgs; };
+} // pkgs.lib.optionalAttrs (version == 4) {
+  # TODO: Test kerberos + nfsv3
+  kerberos = import ./kerberos.nix { inherit version system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/nfs/kerberos.nix b/nixpkgs/nixos/tests/nfs/kerberos.nix
new file mode 100644
index 000000000000..5944b53319a0
--- /dev/null
+++ b/nixpkgs/nixos/tests/nfs/kerberos.nix
@@ -0,0 +1,136 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  security.krb5 = {
+    enable = true;
+    settings = {
+      domain_realm."nfs.test" = "NFS.TEST";
+      libdefaults.default_realm = "NFS.TEST";
+      realms."NFS.TEST" = {
+        admin_server = "server.nfs.test";
+        kdc = "server.nfs.test";
+      };
+    };
+  };
+
+  hosts =
+    ''
+      192.168.1.1 client.nfs.test
+      192.168.1.2 server.nfs.test
+    '';
+
+  users = {
+    users.alice = {
+        isNormalUser = true;
+        name = "alice";
+        uid = 1000;
+      };
+  };
+
+in
+
+{
+  name = "nfsv4-with-kerberos";
+
+  nodes = {
+    client = { lib, ... }:
+      { inherit security users;
+
+        networking.extraHosts = hosts;
+        networking.domain = "nfs.test";
+        networking.hostName = "client";
+
+        virtualisation.fileSystems =
+          { "/data" = {
+              device  = "server.nfs.test:/";
+              fsType  = "nfs";
+              options = [ "nfsvers=4" "sec=krb5p" "noauto" ];
+            };
+          };
+      };
+
+    server = { lib, ...}:
+      { inherit security users;
+
+        networking.extraHosts = hosts;
+        networking.domain = "nfs.test";
+        networking.hostName = "server";
+
+        networking.firewall.allowedTCPPorts = [
+          111  # rpc
+          2049 # nfs
+          88   # kerberos
+          749  # kerberos admin
+        ];
+
+        services.kerberos_server.enable = true;
+        services.kerberos_server.realms =
+          { "NFS.TEST".acl =
+            [ { access = "all"; principal = "admin/admin"; } ];
+          };
+
+        services.nfs.server.enable = true;
+        services.nfs.server.createMountPoints = true;
+        services.nfs.server.exports =
+          ''
+            /data *(rw,no_root_squash,fsid=0,sec=krb5p)
+          '';
+      };
+  };
+
+  testScript =
+    ''
+      server.succeed("mkdir -p /data/alice")
+      server.succeed("chown alice:users /data/alice")
+
+      # set up kerberos database
+      server.succeed(
+          "kdb5_util create -s -r NFS.TEST -P master_key",
+          "systemctl restart kadmind.service kdc.service",
+      )
+      server.wait_for_unit("kadmind.service")
+      server.wait_for_unit("kdc.service")
+
+      # create principals
+      server.succeed(
+          "kadmin.local add_principal -randkey nfs/server.nfs.test",
+          "kadmin.local add_principal -randkey nfs/client.nfs.test",
+          "kadmin.local add_principal -pw admin_pw admin/admin",
+          "kadmin.local add_principal -pw alice_pw alice",
+      )
+
+      # add principals to server keytab
+      server.succeed("kadmin.local ktadd nfs/server.nfs.test")
+      server.succeed("systemctl start rpc-gssd.service rpc-svcgssd.service")
+      server.wait_for_unit("rpc-gssd.service")
+      server.wait_for_unit("rpc-svcgssd.service")
+
+      client.systemctl("start network-online.target")
+      client.wait_for_unit("network-online.target")
+
+      # add principals to client keytab
+      client.succeed("echo admin_pw | kadmin -p admin/admin ktadd nfs/client.nfs.test")
+      client.succeed("systemctl start rpc-gssd.service")
+      client.wait_for_unit("rpc-gssd.service")
+
+      with subtest("nfs share mounts"):
+          client.succeed("systemctl restart data.mount")
+          client.wait_for_unit("data.mount")
+
+      with subtest("permissions on nfs share are enforced"):
+          client.fail("su alice -c 'ls /data'")
+          client.succeed("su alice -c 'echo alice_pw | kinit'")
+          client.succeed("su alice -c 'ls /data'")
+
+          client.fail("su alice -c 'echo bla >> /data/foo'")
+          client.succeed("su alice -c 'echo bla >> /data/alice/foo'")
+          server.succeed("test -e /data/alice/foo")
+
+      with subtest("uids/gids are mapped correctly on nfs share"):
+          ids = client.succeed("stat -c '%U %G' /data/alice").split()
+          expected = ["alice", "users"]
+          assert ids == expected, f"ids incorrect: got {ids} expected {expected}"
+    '';
+
+  meta.maintainers = [ lib.maintainers.dblsaiko ];
+})
diff --git a/nixpkgs/nixos/tests/nfs/simple.nix b/nixpkgs/nixos/tests/nfs/simple.nix
new file mode 100644
index 000000000000..026da9563bc0
--- /dev/null
+++ b/nixpkgs/nixos/tests/nfs/simple.nix
@@ -0,0 +1,95 @@
+import ../make-test-python.nix ({ pkgs, version ? 4, ... }:
+
+let
+
+  client =
+    { pkgs, ... }:
+    { virtualisation.fileSystems =
+        { "/data" =
+           { # nfs4 exports the export with fsid=0 as a virtual root directory
+             device = if (version == 4) then "server:/" else "server:/data";
+             fsType = "nfs";
+             options = [ "vers=${toString version}" ];
+           };
+        };
+      networking.firewall.enable = false; # FIXME: only open statd
+    };
+
+in
+
+{
+  name = "nfs";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes =
+    { client1 = client;
+      client2 = client;
+
+      server =
+        { ... }:
+        { services.nfs.server.enable = true;
+          services.nfs.server.exports =
+            ''
+              /data 192.168.1.0/255.255.255.0(rw,no_root_squash,no_subtree_check,fsid=0)
+            '';
+          services.nfs.server.createMountPoints = true;
+          networking.firewall.enable = false; # FIXME: figure out what ports need to be allowed
+        };
+    };
+
+  testScript =
+    ''
+      import time
+
+      server.wait_for_unit("nfs-server")
+      server.succeed("systemctl start network-online.target")
+      server.wait_for_unit("network-online.target")
+
+      start_all()
+
+      client1.wait_for_unit("data.mount")
+      client1.succeed("echo bla > /data/foo")
+      server.succeed("test -e /data/foo")
+
+      client2.wait_for_unit("data.mount")
+      client2.succeed("echo bla > /data/bar")
+      server.succeed("test -e /data/bar")
+
+      with subtest("restarting 'nfs-server' works correctly"):
+          server.succeed("systemctl restart nfs-server")
+          # will take 90 seconds due to the NFS grace period
+          client2.succeed("echo bla >> /data/bar")
+
+      with subtest("can get a lock"):
+          client2.succeed("time flock -n -s /data/lock true")
+
+      with subtest("client 2 fails to acquire lock held by client 1"):
+          client1.succeed("flock -x /data/lock -c 'touch locked; sleep 100000' >&2 &")
+          client1.wait_for_file("locked")
+          client2.fail("flock -n -s /data/lock true")
+
+      with subtest("client 2 obtains lock after resetting client 1"):
+          client2.succeed(
+              "flock -x /data/lock -c 'echo acquired; touch locked; sleep 100000' >&2 &"
+          )
+          client1.crash()
+          client1.start()
+          client2.wait_for_file("locked")
+
+      with subtest("locks survive server reboot"):
+          client1.wait_for_unit("data.mount")
+          server.shutdown()
+          server.start()
+          client1.succeed("touch /data/xyzzy")
+          client1.fail("time flock -n -s /data/lock true")
+
+      with subtest("unmounting during shutdown happens quickly"):
+          t1 = time.monotonic()
+          client1.shutdown()
+          duration = time.monotonic() - t1
+          # FIXME: regressed in kernel 6.1.28, temporarily disabled while investigating
+          # assert duration < 30, f"shutdown took too long ({duration} seconds)"
+    '';
+})
diff --git a/nixpkgs/nixos/tests/nghttpx.nix b/nixpkgs/nixos/tests/nghttpx.nix
new file mode 100644
index 000000000000..11cac332827d
--- /dev/null
+++ b/nixpkgs/nixos/tests/nghttpx.nix
@@ -0,0 +1,61 @@
+let
+  nginxRoot = "/run/nginx";
+in
+  import ./make-test-python.nix ({...}: {
+    name  = "nghttpx";
+    nodes = {
+      webserver = {
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        systemd.services.nginx = {
+          preStart = ''
+            mkdir -p ${nginxRoot}
+            echo "Hello world!" > ${nginxRoot}/hello-world.txt
+          '';
+        };
+
+        services.nginx = {
+          enable = true;
+          virtualHosts.server = {
+            locations."/".root = nginxRoot;
+          };
+        };
+      };
+
+      proxy = {
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        services.nghttpx = {
+          enable = true;
+          frontends = [
+            { server = {
+                host = "*";
+                port = 80;
+              };
+
+              params = {
+                tls = "no-tls";
+              };
+            }
+          ];
+          backends = [
+            { server = {
+                host = "webserver";
+                port = 80;
+              };
+              patterns = [ "/" ];
+              params.proto = "http/1.1";
+            }
+          ];
+        };
+      };
+
+      client = {};
+    };
+
+    testScript = ''
+      start_all()
+
+      webserver.wait_for_open_port(80)
+      proxy.wait_for_open_port(80)
+      client.wait_until_succeeds("curl -s --fail http://proxy/hello-world.txt")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/nginx-auth.nix b/nixpkgs/nixos/tests/nginx-auth.nix
new file mode 100644
index 000000000000..a85426dda871
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-auth.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx-auth";
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.nginx = let
+        root = pkgs.runCommand "testdir" {} ''
+          mkdir "$out"
+          echo hello world > "$out/index.html"
+        '';
+      in {
+        enable = true;
+
+        virtualHosts.lockedroot = {
+          inherit root;
+          basicAuth.alice = "pwofa";
+        };
+
+        virtualHosts.lockedsubdir = {
+          inherit root;
+          locations."/sublocation/" = {
+            alias = "${root}/";
+            basicAuth.bob = "pwofb";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_port(80)
+
+    webserver.fail("curl --fail --resolve lockedroot:80:127.0.0.1 http://lockedroot")
+    webserver.succeed(
+        "curl --fail --resolve lockedroot:80:127.0.0.1 http://alice:pwofa@lockedroot"
+    )
+
+    webserver.succeed("curl --fail --resolve lockedsubdir:80:127.0.0.1 http://lockedsubdir")
+    webserver.fail(
+        "curl --fail --resolve lockedsubdir:80:127.0.0.1 http://lockedsubdir/sublocation/index.html"
+    )
+    webserver.succeed(
+        "curl --fail --resolve lockedsubdir:80:127.0.0.1 http://bob:pwofb@lockedsubdir/sublocation/index.html"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-etag-compression.nix b/nixpkgs/nixos/tests/nginx-etag-compression.nix
new file mode 100644
index 000000000000..67493ae29984
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-etag-compression.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix {
+  name = "nginx-etag-compression";
+
+  nodes.machine = { pkgs, lib, ... }: {
+    services.nginx = {
+      enable = true;
+      recommendedGzipSettings = true;
+      virtualHosts.default = {
+        root = pkgs.runCommandLocal "testdir" {} ''
+          mkdir "$out"
+          cat > "$out/index.html" <<EOF
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          EOF
+          ${pkgs.gzip}/bin/gzip -k "$out/index.html"
+        '';
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    machine.wait_for_unit("nginx")
+    machine.wait_for_open_port(80)
+
+    etag_plain = machine.succeed("curl -s -w'%header{etag}' -o/dev/null -H 'Accept-encoding:' http://127.0.0.1/")
+    etag_gzip = machine.succeed("curl -s -w'%header{etag}' -o/dev/null -H 'Accept-encoding:gzip' http://127.0.0.1/")
+
+    with subtest("different representations have different etags"):
+      assert etag_plain != etag_gzip, f"etags should differ: {etag_plain} == {etag_gzip}"
+
+    with subtest("etag for uncompressed response is reproducible"):
+      etag_plain_repeat = machine.succeed("curl -s -w'%header{etag}' -o/dev/null -H 'Accept-encoding:' http://127.0.0.1/")
+      assert etag_plain == etag_plain_repeat, f"etags should be the same: {etag_plain} != {etag_plain_repeat}"
+
+    with subtest("etag for compressed response is reproducible"):
+      etag_gzip_repeat = machine.succeed("curl -s -w'%header{etag}' -o/dev/null -H 'Accept-encoding:gzip' http://127.0.0.1/")
+      assert etag_gzip == etag_gzip_repeat, f"etags should be the same: {etag_gzip} != {etag_gzip_repeat}"
+  '';
+}
diff --git a/nixpkgs/nixos/tests/nginx-etag.nix b/nixpkgs/nixos/tests/nginx-etag.nix
new file mode 100644
index 000000000000..6f45eacf8b41
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-etag.nix
@@ -0,0 +1,88 @@
+import ./make-test-python.nix {
+  name = "nginx-etag";
+
+  nodes = {
+    server = { pkgs, lib, ... }: {
+      networking.firewall.enable = false;
+      services.nginx.enable = true;
+      services.nginx.virtualHosts.server = {
+        root = pkgs.runCommandLocal "testdir" {} ''
+          mkdir "$out"
+          cat > "$out/test.js" <<EOF
+          document.getElementById('foobar').setAttribute('foo', 'bar');
+          EOF
+          cat > "$out/index.html" <<EOF
+          <!DOCTYPE html>
+          <div id="foobar">test</div>
+          <script src="test.js"></script>
+          EOF
+        '';
+      };
+
+      specialisation.pass-checks.configuration = {
+        services.nginx.virtualHosts.server = {
+          root = lib.mkForce (pkgs.runCommandLocal "testdir2" {} ''
+            mkdir "$out"
+            cat > "$out/test.js" <<EOF
+            document.getElementById('foobar').setAttribute('foo', 'yay');
+            EOF
+            cat > "$out/index.html" <<EOF
+            <!DOCTYPE html>
+            <div id="foobar">test</div>
+            <script src="test.js"></script>
+            EOF
+          '');
+        };
+      };
+    };
+
+    client = { pkgs, lib, ... }: {
+      environment.systemPackages = let
+        testRunner = pkgs.writers.writePython3Bin "test-runner" {
+          libraries = [ pkgs.python3Packages.selenium ];
+        } ''
+          import os
+          import time
+
+          from selenium.webdriver import Firefox
+          from selenium.webdriver.firefox.options import Options
+
+          options = Options()
+          options.add_argument('--headless')
+          driver = Firefox(options=options)
+
+          driver.implicitly_wait(20)
+          driver.get('http://server/')
+          driver.find_element('xpath', '//div[@foo="bar"]')
+          open('/tmp/passed_stage1', 'w')
+
+          while not os.path.exists('/tmp/proceed'):
+              time.sleep(0.5)
+
+          driver.get('http://server/')
+          driver.find_element('xpath', '//div[@foo="yay"]')
+          open('/tmp/passed', 'w')
+        '';
+      in [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    inherit (nodes.server.config.system.build) toplevel;
+    newSystem = "${toplevel}/specialisation/pass-checks";
+  in ''
+    start_all()
+
+    server.wait_for_unit("nginx.service")
+    client.wait_for_unit("multi-user.target")
+    client.execute("test-runner >&2 &")
+    client.wait_for_file("/tmp/passed_stage1")
+
+    server.succeed(
+        "${newSystem}/bin/switch-to-configuration test >&2"
+    )
+    client.succeed("touch /tmp/proceed")
+
+    client.wait_for_file("/tmp/passed")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/nginx-globalredirect.nix b/nixpkgs/nixos/tests/nginx-globalredirect.nix
new file mode 100644
index 000000000000..5f5f4f344d82
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-globalredirect.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx-globalredirect";
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.nginx = {
+        enable = true;
+        virtualHosts.localhost = {
+          globalRedirect = "other.example.com";
+          # Add an exception
+          locations."/noredirect".return = "200 'foo'";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_port(80)
+
+    webserver.succeed("curl --fail -si http://localhost/alf | grep '^Location:.*/alf'")
+    webserver.fail("curl --fail -si http://localhost/noredirect | grep '^Location:'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-http3.nix b/nixpkgs/nixos/tests/nginx-http3.nix
new file mode 100644
index 000000000000..22f7f61f10ce
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-http3.nix
@@ -0,0 +1,113 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  hosts = ''
+    192.168.2.101 acme.test
+  '';
+
+in
+
+builtins.listToAttrs (
+  builtins.map
+    (nginxPackage:
+      {
+        name = pkgs.lib.getName nginxPackage;
+        value = makeTest {
+          name = "nginx-http3-${pkgs.lib.getName nginxPackage}";
+          meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
+
+          nodes = {
+            server = { lib, pkgs, ... }: {
+              networking = {
+                interfaces.eth1 = {
+                  ipv4.addresses = [
+                    { address = "192.168.2.101"; prefixLength = 24; }
+                  ];
+                };
+                extraHosts = hosts;
+                firewall.allowedTCPPorts = [ 443 ];
+                firewall.allowedUDPPorts = [ 443 ];
+              };
+
+              security.pki.certificates = [
+                (builtins.readFile ./common/acme/server/ca.cert.pem)
+              ];
+
+              services.nginx = {
+                enable = true;
+                package = nginxPackage;
+
+                virtualHosts."acme.test" = {
+                  onlySSL = true;
+                  sslCertificate = ./common/acme/server/acme.test.cert.pem;
+                  sslCertificateKey = ./common/acme/server/acme.test.key.pem;
+                  http2 = true;
+                  http3 = true;
+                  http3_hq = false;
+                  quic = true;
+                  reuseport = true;
+                  root = lib.mkForce (pkgs.runCommandLocal "testdir" {} ''
+                    mkdir "$out"
+                    cat > "$out/index.html" <<EOF
+                    <html><body>Hello World!</body></html>
+                    EOF
+                    cat > "$out/example.txt" <<EOF
+                    Check http3 protocol.
+                    EOF
+                  '');
+                };
+              };
+            };
+
+            client = { pkgs, ... }: {
+              environment.systemPackages = [ pkgs.curlHTTP3 ];
+              networking = {
+                interfaces.eth1 = {
+                  ipv4.addresses = [
+                    { address = "192.168.2.201"; prefixLength = 24; }
+                  ];
+                };
+                extraHosts = hosts;
+              };
+
+              security.pki.certificates = [
+                (builtins.readFile ./common/acme/server/ca.cert.pem)
+              ];
+            };
+          };
+
+          testScript = ''
+            start_all()
+
+            server.wait_for_unit("nginx")
+            server.wait_for_open_port(443)
+
+            # Check http connections
+            client.succeed("curl --verbose --http3-only https://acme.test | grep 'Hello World!'")
+
+            # Check downloadings
+            client.succeed("curl --verbose --http3-only https://acme.test/example.txt --output /tmp/example.txt")
+            client.succeed("cat /tmp/example.txt | grep 'Check http3 protocol.'")
+
+            # Check header reading
+            client.succeed("curl --verbose --http3-only --head https://acme.test | grep 'content-type'")
+            client.succeed("curl --verbose --http3-only --head https://acme.test | grep 'HTTP/3 200'")
+            client.succeed("curl --verbose --http3-only --head https://acme.test/error | grep 'HTTP/3 404'")
+
+            # Check change User-Agent
+            client.succeed("curl --verbose --http3-only --user-agent 'Curl test 3.0' https://acme.test")
+            server.succeed("cat /var/log/nginx/access.log | grep 'Curl test 3.0'")
+
+            server.shutdown()
+            client.shutdown()
+          '';
+        };
+      }
+    )
+    [ pkgs.angieQuic pkgs.nginxQuic ]
+)
diff --git a/nixpkgs/nixos/tests/nginx-modsecurity.nix b/nixpkgs/nixos/tests/nginx-modsecurity.nix
new file mode 100644
index 000000000000..3c41da3e8d9b
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-modsecurity.nix
@@ -0,0 +1,39 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "nginx-modsecurity";
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    services.nginx = {
+      enable = true;
+      additionalModules = [ pkgs.nginxModules.modsecurity ];
+      virtualHosts.localhost =
+        let modsecurity_conf = pkgs.writeText "modsecurity.conf" ''
+          SecRuleEngine On
+          SecDefaultAction "phase:1,log,auditlog,deny,status:403"
+          SecDefaultAction "phase:2,log,auditlog,deny,status:403"
+          SecRule REQUEST_METHOD   "HEAD"        "id:100, phase:1, block"
+          SecRule REQUEST_FILENAME "secret.html" "id:101, phase:2, block"
+        '';
+        testroot = pkgs.runCommand "testroot" {} ''
+          mkdir -p $out
+          echo "<html><body>Hello World!</body></html>" > $out/index.html
+          echo "s3cret" > $out/secret.html
+        '';
+      in {
+        root = testroot;
+        extraConfig = ''
+          modsecurity on;
+          modsecurity_rules_file ${modsecurity_conf};
+        '';
+      };
+    };
+  };
+  testScript = ''
+    machine.wait_for_unit("nginx")
+
+    response = machine.wait_until_succeeds("curl -fvvv -s http://127.0.0.1/")
+    assert "Hello World!" in response
+
+    machine.fail("curl -fvvv -X HEAD -s http://127.0.0.1/")
+    machine.fail("curl -fvvv -s http://127.0.0.1/secret.html")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-moreheaders.nix b/nixpkgs/nixos/tests/nginx-moreheaders.nix
new file mode 100644
index 000000000000..560dcf9ce0b8
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-moreheaders.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix {
+  name = "nginx-more-headers";
+
+  nodes = {
+    webserver = { pkgs, ... }: {
+      services.nginx = {
+        enable = true;
+
+        virtualHosts.test = {
+          locations = {
+            "/".return = "200 blub";
+            "/some" =  {
+              return = "200 blub";
+              extraConfig = ''
+                more_set_headers "Referrer-Policy: no-referrer";
+              '';
+            };
+          };
+          extraConfig = ''
+            more_set_headers "X-Powered-By: nixos";
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_port(80)
+
+    webserver.succeed("curl --fail --resolve test:80:127.0.0.1 --head --verbose http://test | grep -q \"X-Powered-By: nixos\"")
+    webserver.fail("curl --fail --resolve test:80:127.0.0.1 --head --verbose http://test | grep -q \"Referrer-Policy: no-referrer\"")
+
+    webserver.succeed("curl --fail --resolve test:80:127.0.0.1 --head --verbose http://test/some | grep -q \"X-Powered-By: nixos\"")
+    webserver.succeed("curl --fail --resolve test:80:127.0.0.1 --head --verbose http://test/some | grep -q \"Referrer-Policy: no-referrer\"")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/nginx-njs.nix b/nixpkgs/nixos/tests/nginx-njs.nix
new file mode 100644
index 000000000000..72be16384f1b
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-njs.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "nginx-njs";
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    services.nginx = {
+      enable = true;
+      additionalModules = [ pkgs.nginxModules.njs ];
+      commonHttpConfig = ''
+        js_import http from ${builtins.toFile "http.js" ''
+          function hello(r) {
+              r.return(200, "Hello world!");
+          }
+          export default {hello};
+        ''};
+      '';
+      virtualHosts."localhost".locations."/".extraConfig = ''
+        js_content http.hello;
+      '';
+    };
+  };
+  testScript = ''
+    machine.wait_for_unit("nginx")
+
+    response = machine.wait_until_succeeds("curl -fvvv -s http://127.0.0.1/")
+    assert "Hello world!" == response, f"Expected 'Hello world!', got '{response}'"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem b/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem
new file mode 100644
index 000000000000..e5cea72610b9
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLjCCAhagAwIBAgIIP2+4GFxOYMgwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVbWluaWNhIHJvb3QgY2EgNGU3NTJiMB4XDTIzMDEzMDAzNDExOFoXDTQzMDEz
+MDAzNDExOFowFTETMBEGA1UEAwwKKi50ZXN0Lm5peDCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAMarJSCzelnzTMT5GMoIKA/MXBNk5j277uI2Gq2MCky/
+DlBpx+tjSsKsz6QLBduKMF8OH5AgjrVAKQAtsVPDseY0Qcyx/5dgJjkdO4on+DFb
+V0SJ3ZhYPKACrqQ1SaoG+Xup37puw7sVR13J7oNvP6fAYRcjYqCiFC7VMjJNG4dR
+251jvWWidSc7v5CYw2AxrngtBgHeQuyG9QCJ1DRH8h6ioV7IeonwReN7noYtTWh8
+NDjGnw9HH2nYMcL91E+DWCxWVmbC9/orvYOT7u0Orho0t1w9BB0/zzcdojwQpMCv
+HahEmFQmdGbWTuI4caBeaDBJVsSwKlTcxLSS4MAZ0c8CAwEAAaN3MHUwDgYDVR0P
+AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB
+Af8EAjAAMB8GA1UdIwQYMBaAFGyXySYI3gL88d7GHnGMU6wpiBf2MBUGA1UdEQQO
+MAyCCioudGVzdC5uaXgwDQYJKoZIhvcNAQELBQADggEBAJ/DpwiLVBgWyozsn++f
+kR4m0dUjnuCgpHo2EMoMZh+9og+OC0vq6WITXHaJytB3aBMxFOUTim3vwxPyWPXX
+/vy+q6jJ6QMLx1J3VIWZdmXsT+qLGbVzL/4gNoaRsLPGO06p3yVjhas+OBFx1Fee
+6kTHb82S/dzBojOJLRRo18CU9yw0FUXOPqN7HF7k2y+Twe6+iwCuCKGSFcvmRjxe
+bWy11C921bTienW0Rmq6ppFWDaUNYP8kKpMN2ViAvc0tyF6wwk5lyOiqCR+pQHJR
+H/J4qSeKDchYLKECuzd6SySz8FW/xPKogQ28zba+DBD86hpqiEJOBzxbrcN3cjUn
+7N4=
+-----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem b/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem
new file mode 100644
index 000000000000..ed2b17af0bf6
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/_.test.nix.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAxqslILN6WfNMxPkYyggoD8xcE2TmPbvu4jYarYwKTL8OUGnH
+62NKwqzPpAsF24owXw4fkCCOtUApAC2xU8Ox5jRBzLH/l2AmOR07iif4MVtXRInd
+mFg8oAKupDVJqgb5e6nfum7DuxVHXcnug28/p8BhFyNioKIULtUyMk0bh1HbnWO9
+ZaJ1Jzu/kJjDYDGueC0GAd5C7Ib1AInUNEfyHqKhXsh6ifBF43uehi1NaHw0OMaf
+D0cfadgxwv3UT4NYLFZWZsL3+iu9g5Pu7Q6uGjS3XD0EHT/PNx2iPBCkwK8dqESY
+VCZ0ZtZO4jhxoF5oMElWxLAqVNzEtJLgwBnRzwIDAQABAoIBAFuNGOH184cqKJGI
+3RSVJ6kIGtJRKA0A4vfZyPd61nBBhx4lcRyXOCd4LYPCFKP0DZBwWLk5V6pM89gC
+NnqMbxnPsRbcXBVtGJAvWXW0L5rHJfMOuVBwMRfnxIUljVnONv/264PlcUtwZd/h
+o4lsJeBvNg7MnrG5nyVp1+T4RZxYm1P86HLp5zyT+fdj4Cr82b9j6QpxGXEfm1jV
+QA1xr1ZkrV8fgETyaE0TBIKcdt6xNfv1mpI1RE5gaP/YzcCs/mL+G0kMar4l7pO/
+6OHXTvHz+W3G6Xlha7Wq1ADoqYz2K7VoL/OgSQhIxRNujyWR6lir7eladVrKkCzu
+uzFi/HECgYEA0vSNCIK3useSypMPHhYUVNbZ4hbK0WgqSAxfJQtL3nC7KviVMAXj
+IKVR90xuzJB+ih88KCJpH84JH90paMpW0Gq1yEae90bnWa8Nj7ULLS/Zuj0WrelU
++DEGbx47IUPOtiLBxooxFKyIVhX3hWRwZ0pokSQzbgb5zYnlM6tqZ3cCgYEA8Rb2
+wtt0XmqEQedFacs4fobJoVWMcETjpuxYp0m5Kje/4QkptZIbspXGBgNtPBBRGg51
+AYSu8wYkGEueI77KiFDgY8AAkpOk2MrMVPszjOhUiO1oEfbT6ynOY5RDOuXcY6jo
+8RpSk46VkfVxt6LVmappqcVFtVWcAjdGfXeSLmkCgYAWP7SgMSkvidzxgJEXmzyJ
+th9EuSKq81GCR8vBHG/kBf+3iIAzkGtkBgufCXCmIpc1+hVeJkLwF8rekXTMmIqP
+cLG7bbdWXSQJUW0cuvtyyJkuC0NZFELh6knDbmzOFVi33PKS/gAvLgMzER4J843n
+VvGwXSEPeazfAKwrxuhyAQKBgQCOm5TPYlyNVNhy20h18d2zCivOoPn3luhKXtd5
+7OP4kw2PIYpoesqjcnC2MeS1eLlgfli70y5hVqqXLHOYlUzcIWr51iMAkREbo6oG
+QqkVmoAWlsfOiICGRC5vPM4f0sPwt4NCyt05p0fWFKd1hn5u7Ryfba90OfWUYfny
+UX5IsQKBgQCswer4Qc3UepkiYxGwSTxgIh4kYlmamU2I00Kar4uFAr9JsCbk98f0
+kaCUNZjrrvTwgRmdhwcpMDiMW/F4QkNk0I2unHcoAvzNop6c22VhHJU2XJhrQ57h
+n1iPiw0NLXiA4RQwMUMjtt3nqlpLOTXGtsF8TmpWPcAN2QcTxOutzw==
+-----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.cert.pem b/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.cert.pem
new file mode 100644
index 000000000000..c0b2cc8f3df2
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDSzCCAjOgAwIBAgIITnUr3xFw4oEwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
+AxMVbWluaWNhIHJvb3QgY2EgNGU3NTJiMCAXDTIzMDEzMDAzNDExOFoYDzIxMjMw
+MTMwMDM0MTE4WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA0ZTc1MmIwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1SrJT9k3zXIXApEyL5UDlw7F6
+MMOqE5d+8ZwMccHbEKLu0ssNRY+j31tnNYQ/r5iCNeNgUZccKBgzdU0ysyw5n4tw
+0y+MTD9fCfUXYcc8pJRPRolo6zxYO9W7WJr0nfJZ+p7zFRAjRCmzXdnZjKz0EGcg
+x9mHwn//3SuLt1ItK1n3aZ6im9NlcVtunDe3lCSL0tRgy7wDGNvWDZMO49jk4AFU
+BlMqScuiNpUzYgCxNaaGMuH3M0f0YyRAxSs6FWewLtqTIaVql7HL+3PcGAhvlKEZ
+fvfaf80F9aWI88sbEddTA0s5837zEoDwGpZl3K5sPU/O3MVEHIhAY5ICG0IBAgMB
+AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
+BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRsl8kmCN4C/PHe
+xh5xjFOsKYgX9jAfBgNVHSMEGDAWgBRsl8kmCN4C/PHexh5xjFOsKYgX9jANBgkq
+hkiG9w0BAQsFAAOCAQEAmvgpU+q+TBbz+9Y2rdiIeTfeDXtMNPf+nKI3zxYztRGC
+MoKP6jCQaFSQra4BVumFLV38DoqR1pOV1ojkiyO5c/9Iym/1Wmm8LeqgsHNqSgyS
+C7wvBcb/N9PzIBQFq/RiboDoC7bqK/0zQguCmBtGceH+AVpQyfXM+P78B1EkHozu
+67igP8GfouPp2s4Vd5P2XGkA6vMgYCtFEnCbtmmo7C8B+ymhD/D9axpMKQ1OaBg9
+jfqLOlk+Rc2nYZuaDjnUmlTkYjC6EwCNe9weYkSJgQ9QzoGJLIRARsdQdsp3C2fZ
+l2UZKkDJ2GPrrc+TdaGXZTYi0uMmvQsEKZXtqAzorQ==
+-----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.key.pem b/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.key.pem
new file mode 100644
index 000000000000..717948f5b879
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/ca.key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAtUqyU/ZN81yFwKRMi+VA5cOxejDDqhOXfvGcDHHB2xCi7tLL
+DUWPo99bZzWEP6+YgjXjYFGXHCgYM3VNMrMsOZ+LcNMvjEw/Xwn1F2HHPKSUT0aJ
+aOs8WDvVu1ia9J3yWfqe8xUQI0Qps13Z2Yys9BBnIMfZh8J//90ri7dSLStZ92me
+opvTZXFbbpw3t5Qki9LUYMu8Axjb1g2TDuPY5OABVAZTKknLojaVM2IAsTWmhjLh
+9zNH9GMkQMUrOhVnsC7akyGlapexy/tz3BgIb5ShGX732n/NBfWliPPLGxHXUwNL
+OfN+8xKA8BqWZdyubD1PztzFRByIQGOSAhtCAQIDAQABAoIBAQCLeAWs1kWtvTYg
+t8UzspC0slItAKrmgt//hvxYDoPmdewC8yPG+AbDOSfmRKOTIxGeyro79UjdHnNP
+0yQqpvCU/AqYJ7/inR37jXuCG3TdUHfQbSF1F9N6xb1tvYKoQYKaelYiB8g8eUnj
+dYYM+U5tDNlpvJW6/YTfYFUJzWRo3i8jj5lhbkjcJDvdOhVxMXNXJgJAymu1KysE
+N1da2l4fzmuoN82wFE9KMyYSn+LOLWBReQQmXHZPP+2LjRIVrWoFoV49k2Ylp9tH
+yeaFx1Ya/wVx3PRnSW+zebWDcc0bAua9XU3Fi42yRq5iXOyoXHyefDfJoId7+GAO
+IF2qRw9hAoGBAM1O1l4ceOEDsEBh7HWTvmfwVfkXgT6VHeI6LGEjb88FApXgT+wT
+1s1IWVVOigLl9OKQbrjqlg9xgzrPDHYRwu5/Oz3X2WaH6wlF+d+okoqls6sCEAeo
+GfzF3sKOHQyIYjttCXE5G38uhIgVFFFfK97AbUiY8egYBr0zjVXK7xINAoGBAOIN
+1pDBFBQIoKj64opm/G9lJBLUpWLBFdWXhXS6q2jNsdY1mLMRmu/RBaKSfGz7W1a/
+a2WBedjcnTWJ/84tBsn4Qj5tLl8xkcXiN/pslWzg724ZnVsbyxM9KvAdXAma3F0g
+2EsYq8mhvbAEkpE+aoM6jwOJBnMhTRZrNMKN2lbFAoGAHmZWB4lfvLG3H1FgmehO
+gUVs9X0tff7GdgD3IUsF+zlasKaOLv6hB7R2xdLjTJqQMBwCyQ6zOYYtUD/oMHNg
+0b+1HesgHbZybuUVorBrQmxWtjOP/BJABtWlrlkso/Zt1S7H/yPdlm9k4GF+qK3W
+6RzFEcLTzvH/zXQcsV9jFuECgYEAhaX+1KiC0XFkY2OpaoCHAOlAUa3NdjyIRzcF
+XUU8MINkgCxB8qUXAHCJL1wCGoDluL0FpwbM3m1YuR200tYGLIUNzVDJ2Ng6wk8E
+H5fxJGU8ydB1Gzescdx5NWt2Tet0G89ecc/NSTHKL3YUnbDUUm/dvA5YdNscc4PA
+tsIdc60CgYEArvU1MwqGQUTDKUmaM2t3qm70fbwmOViHfyTWpn4aAQR3sK16iJMm
+V+dka62L/VYs5CIbzXvCioyugUMZGJi/zIwrViRzqJQbNnPADAW4lG88UxXqHHAH
+q33ivjgd9omGFb37saKOmR44KmjUIDvSIZF4W3EPwAMEyl5mM31Ryns=
+-----END RSA PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/default.nix b/nixpkgs/nixos/tests/nginx-proxyprotocol/default.nix
new file mode 100644
index 000000000000..2ff7debfcbe2
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/default.nix
@@ -0,0 +1,148 @@
+let
+  certs = import ./snakeoil-certs.nix;
+in
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx-proxyprotocol";
+
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+  };
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      environment.systemPackages = [ pkgs.netcat ];
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+
+      networking.extraHosts = ''
+        127.0.0.5 proxy.test.nix
+        127.0.0.5 noproxy.test.nix
+        127.0.0.3 direct-nossl.test.nix
+        127.0.0.4 unsecure-nossl.test.nix
+        127.0.0.2 direct-noproxy.test.nix
+        127.0.0.1 direct-proxy.test.nix
+      '';
+      services.nginx = {
+        enable = true;
+        defaultListen = [
+          { addr = "127.0.0.1"; proxyProtocol = true; ssl = true; }
+          { addr = "127.0.0.2"; }
+          { addr = "127.0.0.3"; ssl = false; }
+          { addr = "127.0.0.4"; ssl = false; proxyProtocol = true; }
+        ];
+        commonHttpConfig = ''
+          log_format pcombined '(proxy_protocol=$proxy_protocol_addr) - (remote_addr=$remote_addr) - (realip=$realip_remote_addr) - (upstream=) - (remote_user=$remote_user) [$time_local] '
+                        '"$request" $status $body_bytes_sent '
+                        '"$http_referer" "$http_user_agent"';
+          access_log /var/log/nginx/access.log pcombined;
+          error_log /var/log/nginx/error.log;
+        '';
+        virtualHosts =
+        let
+          commonConfig = {
+           locations."/".return = "200 '$remote_addr'";
+           extraConfig = ''
+            set_real_ip_from 127.0.0.5/32;
+            real_ip_header proxy_protocol;
+           '';
+         };
+        in
+        {
+          "*.test.nix" = commonConfig // {
+            sslCertificate = certs."*.test.nix".cert;
+            sslCertificateKey = certs."*.test.nix".key;
+            forceSSL = true;
+          };
+          "direct-nossl.test.nix" = commonConfig;
+          "unsecure-nossl.test.nix" = commonConfig // {
+            extraConfig = ''
+              real_ip_header proxy_protocol;
+            '';
+          };
+        };
+      };
+
+      services.sniproxy = {
+        enable = true;
+        config = ''
+          error_log {
+            syslog daemon
+          }
+          access_log {
+            syslog daemon
+          }
+          listener 127.0.0.5:443 {
+            protocol tls
+            source 127.0.0.5
+          }
+          table {
+            ^proxy\.test\.nix$   127.0.0.1 proxy_protocol
+            ^noproxy\.test\.nix$ 127.0.0.2
+          }
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    def check_origin_ip(src_ip: str, dst_url: str, failure: bool = False, proxy_protocol: bool = False, expected_ip: str | None = None):
+      check = webserver.fail if failure else webserver.succeed
+      if expected_ip is None:
+        expected_ip = src_ip
+
+      return check(f"curl {'--haproxy-protocol' if proxy_protocol else '''} --interface {src_ip} --fail -L {dst_url} | grep '{expected_ip}'")
+
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_unit("sniproxy")
+    # This should be closed by virtue of ssl = true;
+    webserver.wait_for_closed_port(80, "127.0.0.1")
+    # This should be open by virtue of no explicit ssl
+    webserver.wait_for_open_port(80, "127.0.0.2")
+    # This should be open by virtue of ssl = true;
+    webserver.wait_for_open_port(443, "127.0.0.1")
+    # This should be open by virtue of no explicit ssl
+    webserver.wait_for_open_port(443, "127.0.0.2")
+    # This should be open by sniproxy
+    webserver.wait_for_open_port(443, "127.0.0.5")
+    # This should be closed by sniproxy
+    webserver.wait_for_closed_port(80, "127.0.0.5")
+
+    # Sanity checks for the NGINX module
+    # direct-HTTP connection to NGINX without TLS, this checks that ssl = false; works well.
+    check_origin_ip("127.0.0.10", "http://direct-nossl.test.nix/")
+    # webserver.execute("openssl s_client -showcerts -connect direct-noproxy.test.nix:443")
+    # direct-HTTP connection to NGINX with TLS
+    check_origin_ip("127.0.0.10", "http://direct-noproxy.test.nix/")
+    check_origin_ip("127.0.0.10", "https://direct-noproxy.test.nix/")
+    # Well, sniproxy is not listening on 80 and cannot redirect
+    check_origin_ip("127.0.0.10", "http://proxy.test.nix/", failure=True)
+    check_origin_ip("127.0.0.10", "http://noproxy.test.nix/", failure=True)
+
+    # Actual PROXY protocol related tests
+    # Connecting through sniproxy should passthrough the originating IP address.
+    check_origin_ip("127.0.0.10", "https://proxy.test.nix/")
+    # Connecting through sniproxy to a non-PROXY protocol enabled listener should not pass the originating IP address.
+    check_origin_ip("127.0.0.10", "https://noproxy.test.nix/", expected_ip="127.0.0.5")
+
+    # Attack tests against spoofing
+    # Let's try to spoof our IP address by connecting direct-y to the PROXY protocol listener.
+    # FIXME(RaitoBezarius): rewrite it using Python + (Scapy|something else) as this is too much broken unfortunately.
+    # Or wait for upstream curl patch.
+    # def generate_attacker_request(original_ip: str, target_ip: str, dst_url: str):
+    #     return f"""PROXY TCP4 {original_ip} {target_ip} 80 80
+    #     GET / HTTP/1.1
+    #     Host: {dst_url}
+
+    #     """
+    # def spoof(original_ip: str, target_ip: str, dst_url: str, tls: bool = False, expect_failure: bool = True):
+    #   method = webserver.fail if expect_failure else webserver.succeed
+    #   port = 443 if tls else 80
+    #   print(webserver.execute(f"cat <<EOF | nc {target_ip} {port}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF"))
+    #   return method(f"cat <<EOF | nc {target_ip} {port} | grep {original_ip}\n{generate_attacker_request(original_ip, target_ip, dst_url)}\nEOF")
+
+    # check_origin_ip("127.0.0.10", "http://unsecure-nossl.test.nix", proxy_protocol=True)
+    # spoof("1.1.1.1", "127.0.0.4", "direct-nossl.test.nix")
+    # spoof("1.1.1.1", "127.0.0.4", "unsecure-nossl.test.nix", expect_failure=False)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/generate-certs.nix b/nixpkgs/nixos/tests/nginx-proxyprotocol/generate-certs.nix
new file mode 100644
index 000000000000..b2315062035e
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/generate-certs.nix
@@ -0,0 +1,30 @@
+# Minica can provide a CA key and cert, plus a key
+# and cert for our fake CA server's Web Front End (WFE).
+{
+  pkgs ? import <nixpkgs> {},
+  minica ? pkgs.minica,
+  runCommandCC ? pkgs.runCommandCC,
+}:
+let
+  conf = import ./snakeoil-certs.nix;
+  domain = conf.domain;
+  domainSanitized = pkgs.lib.replaceStrings ["*"] ["_"] domain;
+in
+  runCommandCC "generate-tests-certs" {
+    buildInputs = [ (minica.overrideAttrs (old: {
+    postPatch = ''
+      sed -i 's_NotAfter: time.Now().AddDate(2, 0, 30),_NotAfter: time.Now().AddDate(20, 0, 0),_' main.go
+    '';
+  })) ];
+
+  } ''
+    minica \
+      --ca-key ca.key.pem \
+      --ca-cert ca.cert.pem \
+      --domains "${domain}"
+
+    mkdir -p $out
+    mv ca.*.pem $out/
+    mv ${domainSanitized}/key.pem $out/${domainSanitized}.key.pem
+    mv ${domainSanitized}/cert.pem $out/${domainSanitized}.cert.pem
+  ''
diff --git a/nixpkgs/nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix b/nixpkgs/nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix
new file mode 100644
index 000000000000..61af6351ca65
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-proxyprotocol/snakeoil-certs.nix
@@ -0,0 +1,14 @@
+let
+  domain = "*.test.nix";
+  domainSanitized = "_.test.nix";
+in {
+  inherit domain;
+  ca = {
+    cert = ./ca.cert.pem;
+    key = ./ca.key.pem;
+  };
+  "${domain}" = {
+    cert = ./. + "/${domainSanitized}.cert.pem";
+    key = ./. + "/${domainSanitized}.key.pem";
+  };
+}
diff --git a/nixpkgs/nixos/tests/nginx-pubhtml.nix b/nixpkgs/nixos/tests/nginx-pubhtml.nix
new file mode 100644
index 000000000000..bff24c99d41a
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-pubhtml.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix {
+  name = "nginx-pubhtml";
+
+  nodes.machine = { pkgs, ... }: {
+    systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
+    services.nginx.enable = true;
+    services.nginx.virtualHosts.localhost = {
+      locations."~ ^/\\~([a-z0-9_]+)(/.*)?$".alias = "/home/$1/public_html$2";
+    };
+    users.users.foo.isNormalUser = true;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("nginx")
+    machine.wait_for_open_port(80)
+    machine.succeed("chmod 0711 /home/foo")
+    machine.succeed("su -c 'mkdir -p /home/foo/public_html' foo")
+    machine.succeed("su -c 'echo bar > /home/foo/public_html/bar.txt' foo")
+    machine.succeed('test "$(curl -fvvv http://localhost/~foo/bar.txt)" = bar')
+  '';
+}
diff --git a/nixpkgs/nixos/tests/nginx-redirectcode.nix b/nixpkgs/nixos/tests/nginx-redirectcode.nix
new file mode 100644
index 000000000000..f60434a21a85
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-redirectcode.nix
@@ -0,0 +1,25 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "nginx-redirectcode";
+  meta.maintainers = with lib.maintainers; [ misterio77 ];
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.nginx = {
+        enable = true;
+        virtualHosts.localhost = {
+          globalRedirect = "example.com/foo";
+          # With 308 (and 307), the method and body are to be kept when following it
+          redirectCode = 308;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_port(80)
+
+    # Check the status code
+    webserver.succeed("curl -si http://localhost | grep '^HTTP/[0-9.]\+ 308 Permanent Redirect'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-sso.nix b/nixpkgs/nixos/tests/nginx-sso.nix
new file mode 100644
index 000000000000..221c5f4ed905
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-sso.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx-sso";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ delroth ];
+  };
+
+  nodes.machine = {
+    services.nginx.sso = {
+      enable = true;
+      configuration = {
+        listen = { addr = "127.0.0.1"; port = 8080; };
+
+        providers.token.tokens = {
+          myuser = "MyToken";
+        };
+
+        acl = {
+          rule_sets = [
+            {
+              rules = [ { field = "x-application"; equals = "MyApp"; } ];
+              allow = [ "myuser" ];
+            }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("nginx-sso.service")
+    machine.wait_for_open_port(8080)
+
+    with subtest("No valid user -> 401"):
+        machine.fail("curl -sSf http://localhost:8080/auth")
+
+    with subtest("Valid user but no matching ACL -> 403"):
+        machine.fail(
+            "curl -sSf -H 'Authorization: Token MyToken' http://localhost:8080/auth"
+        )
+
+    with subtest("Valid user and matching ACL -> 200"):
+        machine.succeed(
+            "curl -sSf -H 'Authorization: Token MyToken' -H 'X-Application: MyApp' http://localhost:8080/auth"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-status-page.nix b/nixpkgs/nixos/tests/nginx-status-page.nix
new file mode 100644
index 000000000000..ff2c0940379c
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-status-page.nix
@@ -0,0 +1,72 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx-status-page";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ h7x4 ];
+  };
+
+  nodes = {
+    webserver = { ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.1/24";
+      };
+
+      services.nginx = {
+        enable = true;
+        statusPage = true;
+        virtualHosts."localhost".locations."/index.html".return = "200 'hello world\n'";
+      };
+
+      environment.systemPackages = with pkgs; [ curl ];
+    };
+
+    client = { ... }: {
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.2/24";
+      };
+
+      environment.systemPackages = with pkgs; [ curl ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_port(80)
+
+    def expect_http_code(node, code, url):
+        http_code = node.succeed(f"curl -w '%{{http_code}}' '{url}'")
+        assert http_code.split("\n")[-1].strip() == code, \
+          f"expected {code} but got following response:\n{http_code}"
+
+    with subtest("localhost can access status page"):
+        expect_http_code(webserver, "200", "http://localhost/nginx_status")
+
+    with subtest("localhost can access other page"):
+        expect_http_code(webserver, "200", "http://localhost/index.html")
+
+    with subtest("client can not access status page"):
+        expect_http_code(client, "403", "http://10.0.0.1/nginx_status")
+
+    with subtest("client can access other page"):
+        expect_http_code(client, "200", "http://10.0.0.1/index.html")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-tmpdir.nix b/nixpkgs/nixos/tests/nginx-tmpdir.nix
new file mode 100644
index 000000000000..f26f992ffe1b
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-tmpdir.nix
@@ -0,0 +1,60 @@
+let
+  dst-dir = "/run/nginx-test-tmpdir-uploads";
+in
+  import ./make-test-python.nix {
+    name = "nginx-tmpdir";
+
+    nodes.machine = { pkgs, ... }: {
+      environment.etc."tmpfiles.d/nginx-uploads.conf".text = "d ${dst-dir} 0755 nginx nginx 1d";
+
+      # overwrite the tmp.conf with a short age, there will be a duplicate line info from systemd-tmpfiles in the log
+      systemd.tmpfiles.rules = [
+        "q /tmp 1777 root root 1min"
+      ];
+
+      services.nginx.enable = true;
+      # simple upload service using the nginx client body temp path
+      services.nginx.virtualHosts = {
+        localhost = {
+          locations."~ ^/upload/([0-9a-zA-Z-.]*)$" = {
+            extraConfig = ''
+              alias ${dst-dir}/$1;
+              client_body_in_file_only clean;
+              dav_methods PUT;
+              create_full_put_path on;
+              dav_access group:rw all:r;
+            '';
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("nginx")
+      machine.wait_for_open_port(80)
+
+      with subtest("Needed prerequisite --http-client-body-temp-path=/tmp/nginx_client_body and private temp"):
+        machine.succeed("touch /tmp/systemd-private-*-nginx.service-*/tmp/nginx_client_body")
+
+      with subtest("Working upload of test setup"):
+        machine.succeed("curl -X PUT http://localhost/upload/test1 --fail --data-raw 'Raw data 1'")
+        machine.succeed('test "$(cat ${dst-dir}/test1)" = "Raw data 1"')
+
+      # let the tmpfiles clean service do its job
+      machine.succeed("touch /tmp/touched")
+      machine.wait_until_succeeds(
+        "sleep 15 && systemctl start systemd-tmpfiles-clean.service && [ ! -f /tmp/touched ]",
+        timeout=150
+      )
+
+      with subtest("Working upload after cleaning"):
+        machine.succeed("curl -X PUT http://localhost/upload/test2 --fail --data-raw 'Raw data 2'")
+        machine.succeed('test "$(cat ${dst-dir}/test2)" = "Raw data 2"')
+
+      # manually remove the nginx temp dir
+      machine.succeed("rm -r --interactive=never /tmp/systemd-private-*-nginx.service-*/tmp/nginx_client_body")
+
+      with subtest("Broken upload after manual temp dir removal"):
+        machine.fail("curl -X PUT http://localhost/upload/test3 --fail --data-raw 'Raw data 3'")
+    '';
+  }
diff --git a/nixpkgs/nixos/tests/nginx-unix-socket.nix b/nixpkgs/nixos/tests/nginx-unix-socket.nix
new file mode 100644
index 000000000000..4640eaa171bd
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-unix-socket.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  nginxSocketPath = "/var/run/nginx/test.sock";
+in
+{
+  name = "nginx-unix-socket";
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.nginx = {
+        enable = true;
+        virtualHosts.localhost = {
+          serverName = "localhost";
+          listen = [{ addr = "unix:${nginxSocketPath}"; }];
+          locations."/test".return = "200 'foo'";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_unix_socket("${nginxSocketPath}")
+
+    webserver.succeed("curl --fail --silent --unix-socket '${nginxSocketPath}' http://localhost/test | grep '^foo$'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nginx-variants.nix b/nixpkgs/nixos/tests/nginx-variants.nix
new file mode 100644
index 000000000000..8c24052aacce
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx-variants.nix
@@ -0,0 +1,33 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+builtins.listToAttrs (
+  builtins.map
+    (nginxPackage:
+      {
+        name = pkgs.lib.getName nginxPackage;
+        value = makeTest {
+          name = "nginx-variant-${pkgs.lib.getName nginxPackage}";
+
+          nodes.machine = { pkgs, ... }: {
+            services.nginx = {
+              enable = true;
+              virtualHosts.localhost.locations."/".return = "200 'foo'";
+              package = nginxPackage;
+            };
+          };
+
+          testScript = ''
+            machine.wait_for_unit("nginx")
+            machine.wait_for_open_port(80)
+            machine.succeed('test "$(curl -fvvv http://localhost/)" = foo')
+          '';
+        };
+      }
+    )
+    [ pkgs.angie pkgs.angieQuic pkgs.nginxStable pkgs.nginxMainline pkgs.nginxQuic pkgs.nginxShibboleth pkgs.openresty pkgs.tengine ]
+)
diff --git a/nixpkgs/nixos/tests/nginx.nix b/nixpkgs/nixos/tests/nginx.nix
new file mode 100644
index 000000000000..8b1f921ec520
--- /dev/null
+++ b/nixpkgs/nixos/tests/nginx.nix
@@ -0,0 +1,137 @@
+# verifies:
+#   1. nginx generates config file with shared http context definitions above
+#      generated virtual hosts config.
+#   2. whether the ETag header is properly generated whenever we're serving
+#      files in Nix store paths
+#   3. nginx doesn't restart on configuration changes (only reloads)
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nginx";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mbbx6spp danbst ];
+  };
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.nginx.enable = true;
+      services.nginx.commonHttpConfig = ''
+        log_format ceeformat '@cee: {"status":"$status",'
+          '"request_time":$request_time,'
+          '"upstream_response_time":$upstream_response_time,'
+          '"pipe":"$pipe","bytes_sent":$bytes_sent,'
+          '"connection":"$connection",'
+          '"remote_addr":"$remote_addr",'
+          '"host":"$host",'
+          '"timestamp":"$time_iso8601",'
+          '"request":"$request",'
+          '"http_referer":"$http_referer",'
+          '"upstream_addr":"$upstream_addr"}';
+      '';
+      services.nginx.virtualHosts."0.my.test" = {
+        extraConfig = ''
+          access_log syslog:server=unix:/dev/log,facility=user,tag=mytag,severity=info ceeformat;
+          location /favicon.ico { allow all; access_log off; log_not_found off; }
+        '';
+      };
+
+      services.nginx.virtualHosts.localhost = {
+        root = pkgs.runCommand "testdir" {} ''
+          mkdir "$out"
+          echo hello world > "$out/index.html"
+        '';
+      };
+
+      services.nginx.enableReload = true;
+
+      specialisation.etagSystem.configuration = {
+        services.nginx.virtualHosts.localhost = {
+          root = lib.mkForce (pkgs.runCommand "testdir2" {} ''
+            mkdir "$out"
+            echo content changed > "$out/index.html"
+          '');
+        };
+      };
+
+      specialisation.justReloadSystem.configuration = {
+        services.nginx.virtualHosts."1.my.test".listen = [ { addr = "127.0.0.1"; port = 8080; }];
+      };
+
+      specialisation.reloadRestartSystem.configuration = {
+        services.nginx.package = pkgs.nginxMainline;
+      };
+
+      specialisation.reloadWithErrorsSystem.configuration = {
+        services.nginx.package = pkgs.nginxMainline;
+        services.nginx.virtualHosts."!@$$(#*%".locations."~@#*$*!)".proxyPass = ";;;";
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    etagSystem = "${nodes.webserver.system.build.toplevel}/specialisation/etagSystem";
+    justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/justReloadSystem";
+    reloadRestartSystem = "${nodes.webserver.system.build.toplevel}/specialisation/reloadRestartSystem";
+    reloadWithErrorsSystem = "${nodes.webserver.system.build.toplevel}/specialisation/reloadWithErrorsSystem";
+  in ''
+    url = "http://localhost/index.html"
+
+
+    def check_etag():
+        etag = webserver.succeed(
+            f'curl -v {url} 2>&1 | sed -n -e "s/^< etag: *//ip"'
+        ).rstrip()
+        http_code = webserver.succeed(
+            f"curl -w '%{{http_code}}' --head --fail -H 'If-None-Match: {etag}' {url}"
+        )
+        assert http_code.split("\n")[-1] == "304"
+
+        return etag
+
+
+    def wait_for_nginx_on_port(port):
+        webserver.wait_for_unit("nginx")
+        webserver.wait_for_open_port(port)
+
+
+    # nginx can be ready before multi-user.target, in which case switching to
+    # a different configuration might not realize it needs to restart nginx.
+    webserver.wait_for_unit("multi-user.target")
+
+    wait_for_nginx_on_port(80)
+
+    with subtest("check ETag if serving Nix store paths"):
+        old_etag = check_etag()
+        webserver.succeed(
+            "${etagSystem}/bin/switch-to-configuration test >&2"
+        )
+        wait_for_nginx_on_port(80)
+        new_etag = check_etag()
+        assert old_etag != new_etag
+
+    with subtest("config is reloaded on nixos-rebuild switch"):
+        webserver.succeed(
+            "${justReloadSystem}/bin/switch-to-configuration test >&2"
+        )
+        wait_for_nginx_on_port(8080)
+        webserver.fail("journalctl -u nginx | grep -q -i stopped")
+        webserver.succeed("journalctl -u nginx | grep -q -i reloaded")
+
+    with subtest("restart when nginx package changes"):
+        webserver.succeed(
+            "${reloadRestartSystem}/bin/switch-to-configuration test >&2"
+        )
+        wait_for_nginx_on_port(80)
+        webserver.succeed("journalctl -u nginx | grep -q -i stopped")
+
+    with subtest("nixos-rebuild --switch should fail when there are configuration errors"):
+        webserver.fail(
+            "${reloadWithErrorsSystem}/bin/switch-to-configuration test >&2"
+        )
+        webserver.succeed("[[ $(systemctl is-failed nginx-config-reload) == failed ]]")
+        webserver.succeed("[[ $(systemctl is-failed nginx) == active ]]")
+        # just to make sure operation is idempotent. During development I had a situation
+        # when first time it shows error, but stops showing it on subsequent rebuilds
+        webserver.fail(
+            "${reloadWithErrorsSystem}/bin/switch-to-configuration test >&2"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nitter.nix b/nixpkgs/nixos/tests/nitter.nix
new file mode 100644
index 000000000000..114f1aac7c7a
--- /dev/null
+++ b/nixpkgs/nixos/tests/nitter.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  # In a real deployment this should naturally not common from the nix store
+  # and be seeded via agenix or as a non-nix managed file.
+  #
+  # These credentials are from the nitter wiki and are expired. We must provide
+  # credentials in the correct format, otherwise nitter fails to start. They
+  # must not be valid, as unauthorized errors are handled gracefully.
+  guestAccountFile = pkgs.writeText "guest_accounts.jsonl" ''
+    {"oauth_token":"1719213587296620928-BsXY2RIJEw7fjxoNwbBemgjJhueK0m","oauth_token_secret":"N0WB0xhL4ng6WTN44aZO82SUJjz7ssI3hHez2CUhTiYqy"}
+  '';
+in
+{
+  name = "nitter";
+  meta.maintainers = with pkgs.lib.maintainers; [ erdnaxe ];
+
+  nodes.machine = {
+    services.nitter = {
+      enable = true;
+      # Test CAP_NET_BIND_SERVICE
+      server.port = 80;
+      # Provide dummy guest accounts
+      guestAccounts = guestAccountFile;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("nitter.service")
+    machine.wait_for_open_port(80)
+    machine.succeed("curl --fail http://localhost:80/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nix-ld.nix b/nixpkgs/nixos/tests/nix-ld.nix
new file mode 100644
index 000000000000..8733f5b0c397
--- /dev/null
+++ b/nixpkgs/nixos/tests/nix-ld.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ lib, pkgs, ...} :
+{
+  name = "nix-ld";
+  nodes.machine = { pkgs, ... }: {
+    programs.nix-ld.enable = true;
+    environment.systemPackages = [
+      (pkgs.runCommand "patched-hello" {} ''
+        install -D -m755 ${pkgs.hello}/bin/hello $out/bin/hello
+        patchelf $out/bin/hello --set-interpreter $(cat ${pkgs.nix-ld}/nix-support/ldpath)
+      '')
+    ];
+  };
+  testScript = ''
+    start_all()
+    machine.succeed("hello")
+ '';
+})
diff --git a/nixpkgs/nixos/tests/nix-serve-ssh.nix b/nixpkgs/nixos/tests/nix-serve-ssh.nix
new file mode 100644
index 000000000000..1eb8d5b395b1
--- /dev/null
+++ b/nixpkgs/nixos/tests/nix-serve-ssh.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let inherit (import ./ssh-keys.nix pkgs)
+      snakeOilPrivateKey snakeOilPublicKey;
+    ssh-config = builtins.toFile "ssh.conf" ''
+      UserKnownHostsFile=/dev/null
+      StrictHostKeyChecking=no
+    '';
+in
+   { name = "nix-ssh-serve";
+     meta.maintainers = [ lib.maintainers.shlevy ];
+     nodes =
+       { server.nix.sshServe =
+           { enable = true;
+             keys = [ snakeOilPublicKey ];
+             protocol = "ssh-ng";
+           };
+         server.nix.package = pkgs.nix;
+         client.nix.package = pkgs.nix;
+       };
+     testScript = ''
+       start_all()
+
+       client.succeed("mkdir -m 700 /root/.ssh")
+       client.succeed(
+           "cat ${ssh-config} > /root/.ssh/config"
+       )
+       client.succeed(
+           "cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa"
+       )
+       client.succeed("chmod 600 /root/.ssh/id_ecdsa")
+
+       client.succeed("nix-store --add /etc/machine-id > mach-id-path")
+
+       server.wait_for_unit("sshd")
+
+       client.fail("diff /root/other-store$(cat mach-id-path) /etc/machine-id")
+       # Currently due to shared store this is a noop :(
+       client.succeed("nix copy --experimental-features 'nix-command' --to ssh-ng://nix-ssh@server $(cat mach-id-path)")
+       client.succeed(
+           "nix-store --realise $(cat mach-id-path) --store /root/other-store --substituters ssh-ng://nix-ssh@server"
+       )
+       client.succeed("diff /root/other-store$(cat mach-id-path) /etc/machine-id")
+     '';
+   }
+)
diff --git a/nixpkgs/nixos/tests/nix-serve.nix b/nixpkgs/nixos/tests/nix-serve.nix
new file mode 100644
index 000000000000..3aa913f81107
--- /dev/null
+++ b/nixpkgs/nixos/tests/nix-serve.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "nix-serve";
+  nodes.machine = { pkgs, ... }: {
+    services.nix-serve.enable = true;
+    environment.systemPackages = [
+      pkgs.hello
+    ];
+  };
+  testScript = let
+    pkgHash = builtins.head (
+      builtins.match "${builtins.storeDir}/([^-]+).+" (toString pkgs.hello)
+    );
+  in ''
+    start_all()
+    machine.wait_for_unit("nix-serve.service")
+    machine.wait_for_open_port(5000)
+    machine.succeed(
+        "curl --fail -g http://0.0.0.0:5000/nar/${pkgHash}.nar -o /tmp/hello.nar"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nixops/default.nix b/nixpkgs/nixos/tests/nixops/default.nix
new file mode 100644
index 000000000000..6501d13a2ed3
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixops/default.nix
@@ -0,0 +1,114 @@
+{ pkgs, ... }:
+let
+  inherit (pkgs) lib;
+
+  tests = {
+    # TODO: uncomment stable
+    #  - Blocked on https://github.com/NixOS/nixpkgs/issues/138584 which has a
+    #    PR in staging: https://github.com/NixOS/nixpkgs/pull/139986
+    #  - Alternatively, blocked on a NixOps 2 release
+    #    https://github.com/NixOS/nixops/issues/1242
+    # stable = testsLegacyNetwork { nixopsPkg = pkgs.nixops; };
+    unstable = testsForPackage { nixopsPkg = pkgs.nixops_unstable; };
+
+    # inherit testsForPackage;
+  };
+
+  testsForPackage = args: lib.recurseIntoAttrs {
+    legacyNetwork = testLegacyNetwork args;
+    passthru.override = args': testsForPackage (args // args');
+  };
+
+  testLegacyNetwork = { nixopsPkg, ... }: pkgs.testers.nixosTest ({
+    name = "nixops-legacy-network";
+    nodes = {
+      deployer = { config, lib, nodes, pkgs, ... }: {
+        imports = [ ../../modules/installer/cd-dvd/channel.nix ];
+        environment.systemPackages = [ nixopsPkg ];
+        nix.settings.substituters = lib.mkForce [ ];
+        users.users.person.isNormalUser = true;
+        virtualisation.writableStore = true;
+        virtualisation.additionalPaths = [
+          pkgs.hello
+          pkgs.figlet
+        ];
+
+        # TODO: make this efficient, https://github.com/NixOS/nixpkgs/issues/180529
+        system.includeBuildDependencies = true;
+      };
+      server = { lib, ... }: {
+        imports = [ ./legacy/base-configuration.nix ];
+      };
+    };
+
+    testScript = { nodes }:
+      let
+        deployerSetup = pkgs.writeScript "deployerSetup" ''
+          #!${pkgs.runtimeShell}
+          set -eux -o pipefail
+          cp --no-preserve=mode -r ${./legacy} unicorn
+          cp --no-preserve=mode ${../ssh-keys.nix} unicorn/ssh-keys.nix
+          mkdir -p ~/.ssh
+          cp ${snakeOilPrivateKey} ~/.ssh/id_ed25519
+          chmod 0400 ~/.ssh/id_ed25519
+        '';
+        serverNetworkJSON = pkgs.writeText "server-network.json"
+          (builtins.toJSON nodes.server.system.build.networkConfig);
+      in
+      ''
+        import shlex
+
+        def deployer_do(cmd):
+            cmd = shlex.quote(cmd)
+            return deployer.succeed(f"su person -l -c {cmd} &>/dev/console")
+
+        start_all()
+
+        deployer_do("cat /etc/hosts")
+
+        deployer_do("${deployerSetup}")
+        deployer_do("cp ${serverNetworkJSON} unicorn/server-network.json")
+
+        # Establish that ssh works, regardless of nixops
+        # Easy way to accept the server host key too.
+        server.wait_for_open_port(22)
+        deployer.wait_for_unit("network.target")
+
+        # Put newlines on console, to flush the console reader's line buffer
+        # in case nixops' last output did not end in a newline, as is the case
+        # with a status line (if implemented?)
+        deployer.succeed("while sleep 60s; do echo [60s passed]; done >&2 &")
+
+        deployer_do("cd ~/unicorn; ssh -oStrictHostKeyChecking=accept-new root@server echo hi")
+
+        # Create and deploy
+        deployer_do("cd ~/unicorn; nixops create")
+
+        deployer_do("cd ~/unicorn; nixops deploy --confirm")
+
+        deployer_do("cd ~/unicorn; nixops ssh server 'hello | figlet'")
+      '';
+  });
+
+  inherit (import ../ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+
+  /*
+    Return a store path with a closure containing everything including
+    derivations and all build dependency outputs, all the way down.
+  */
+  allDrvOutputs = pkg:
+    let name = "allDrvOutputs-${pkg.pname or pkg.name or "unknown"}";
+    in
+    pkgs.runCommand name { refs = pkgs.writeReferencesToFile pkg.drvPath; } ''
+      touch $out
+      while read ref; do
+        case $ref in
+          *.drv)
+            cat $ref >>$out
+            ;;
+        esac
+      done <$refs
+    '';
+
+in
+tests
diff --git a/nixpkgs/nixos/tests/nixops/legacy/base-configuration.nix b/nixpkgs/nixos/tests/nixops/legacy/base-configuration.nix
new file mode 100644
index 000000000000..7f1c07a5c4a9
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixops/legacy/base-configuration.nix
@@ -0,0 +1,31 @@
+{ lib, modulesPath, pkgs, ... }:
+let
+  ssh-keys =
+    if builtins.pathExists ../../ssh-keys.nix
+    then # Outside sandbox
+      ../../ssh-keys.nix
+    else # In sandbox
+      ./ssh-keys.nix;
+
+  inherit (import ssh-keys pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+in
+{
+  imports = [
+    (modulesPath + "/virtualisation/qemu-vm.nix")
+    (modulesPath + "/testing/test-instrumentation.nix")
+  ];
+  virtualisation.writableStore = true;
+  nix.settings.substituters = lib.mkForce [ ];
+  virtualisation.graphics = false;
+  documentation.enable = false;
+  services.qemuGuest.enable = true;
+  boot.loader.grub.enable = false;
+
+  services.openssh.enable = true;
+  users.users.root.openssh.authorizedKeys.keys = [
+    snakeOilPublicKey
+  ];
+  security.pam.services.sshd.limits =
+    [{ domain = "*"; item = "memlock"; type = "-"; value = 1024; }];
+}
diff --git a/nixpkgs/nixos/tests/nixops/legacy/nixops.nix b/nixpkgs/nixos/tests/nixops/legacy/nixops.nix
new file mode 100644
index 000000000000..795dc2a71825
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixops/legacy/nixops.nix
@@ -0,0 +1,15 @@
+{
+  network = {
+    description = "Legacy Network using <nixpkgs> and legacy state.";
+    # NB this is not really what makes it a legacy network; lack of flakes is.
+    storage.legacy = { };
+  };
+  server = { lib, pkgs, ... }: {
+    deployment.targetEnv = "none";
+    imports = [
+      ./base-configuration.nix
+      (lib.modules.importJSON ./server-network.json)
+    ];
+    environment.systemPackages = [ pkgs.hello pkgs.figlet ];
+  };
+}
diff --git a/nixpkgs/nixos/tests/nixos-generate-config.nix b/nixpkgs/nixos/tests/nixos-generate-config.nix
new file mode 100644
index 000000000000..e1c2f29e0673
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-generate-config.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix ({ lib, ... } : {
+  name = "nixos-generate-config";
+  meta.maintainers = with lib.maintainers; [ basvandijk ];
+  nodes.machine = {
+    system.nixos-generate-config.configuration = ''
+      # OVERRIDDEN
+      { config, pkgs, ... }: {
+        imports = [ ./hardware-configuration.nix ];
+      $bootLoaderConfig
+      $desktopConfiguration
+      }
+    '';
+
+    system.nixos-generate-config.desktopConfiguration = [''
+      # DESKTOP
+      services.xserver.displayManager.gdm.enable = true;
+      services.xserver.desktopManager.gnome.enable = true;
+    ''];
+  };
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("nixos-generate-config")
+
+    # Test if the configuration really is overridden
+    machine.succeed("grep 'OVERRIDDEN' /etc/nixos/configuration.nix")
+
+    # Test if desktop configuration really is overridden
+    machine.succeed("grep 'DESKTOP' /etc/nixos/configuration.nix")
+
+    # Test of if the Perl variable $bootLoaderConfig is spliced correctly:
+    machine.succeed(
+        "grep 'boot\\.loader\\.grub\\.enable = true;' /etc/nixos/configuration.nix"
+    )
+
+    # Test if the Perl variable $desktopConfiguration is spliced correctly
+    machine.succeed(
+        "grep 'services\\.xserver\\.desktopManager\\.gnome\\.enable = true;' /etc/nixos/configuration.nix"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nixos-rebuild-install-bootloader.nix b/nixpkgs/nixos/tests/nixos-rebuild-install-bootloader.nix
new file mode 100644
index 000000000000..3ade90ea24a7
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-rebuild-install-bootloader.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nixos-rebuild-install-bootloader";
+
+  nodes = {
+    machine = { lib, pkgs, ... }: {
+      imports = [
+        ../modules/profiles/installation-device.nix
+        ../modules/profiles/base.nix
+      ];
+
+      nix.settings = {
+        substituters = lib.mkForce [ ];
+        hashed-mirrors = null;
+        connect-timeout = 1;
+      };
+
+      system.includeBuildDependencies = true;
+
+      virtualisation = {
+        cores = 2;
+        memorySize = 2048;
+      };
+
+      virtualisation.useBootLoader = true;
+    };
+  };
+
+  testScript =
+    let
+      configFile = pkgs.writeText "configuration.nix" ''
+        { lib, pkgs, ... }: {
+          imports = [
+            ./hardware-configuration.nix
+            <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          ];
+
+          boot.loader.grub = {
+            enable = true;
+            device = "/dev/vda";
+            forceInstall = true;
+          };
+
+          documentation.enable = false;
+        }
+      '';
+
+    in
+    ''
+      machine.start()
+      machine.succeed("udevadm settle")
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("nixos-generate-config")
+      machine.copy_from_host(
+          "${configFile}",
+          "/etc/nixos/configuration.nix",
+      )
+      machine.succeed("nixos-rebuild switch")
+
+      # Need to run `nixos-rebuild` twice because the first run will install
+      # GRUB anyway
+      with subtest("Switch system again and install bootloader"):
+          result = machine.succeed("nixos-rebuild switch --install-bootloader")
+          # install-grub2.pl messages
+          assert "updating GRUB 2 menu..." in result
+          assert "installing the GRUB 2 boot loader on /dev/vda..." in result
+          # GRUB message
+          assert "Installation finished. No error reported." in result
+          # at this point we've tested regression #262724, but haven't tested the bootloader itself
+          # TODO: figure out how to how to tell the test driver to start the bootloader instead of
+          # booting into the kernel directly.
+    '';
+})
diff --git a/nixpkgs/nixos/tests/nixos-rebuild-specialisations.nix b/nixpkgs/nixos/tests/nixos-rebuild-specialisations.nix
new file mode 100644
index 000000000000..9192b8a8a030
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-rebuild-specialisations.nix
@@ -0,0 +1,120 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nixos-rebuild-specialisations";
+
+  nodes = {
+    machine = { lib, pkgs, ... }: {
+      imports = [
+        ../modules/profiles/installation-device.nix
+        ../modules/profiles/base.nix
+      ];
+
+      nix.settings = {
+        substituters = lib.mkForce [ ];
+        hashed-mirrors = null;
+        connect-timeout = 1;
+      };
+
+      system.includeBuildDependencies = true;
+
+      system.extraDependencies = [
+        # Not part of the initial build apparently?
+        pkgs.grub2
+      ];
+
+      virtualisation = {
+        cores = 2;
+        memorySize = 4096;
+      };
+    };
+  };
+
+  testScript =
+    let
+      configFile = pkgs.writeText "configuration.nix" ''
+        { lib, pkgs, ... }: {
+          imports = [
+            ./hardware-configuration.nix
+            <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          ];
+
+          boot.loader.grub = {
+            enable = true;
+            device = "/dev/vda";
+            forceInstall = true;
+          };
+
+          documentation.enable = false;
+
+          environment.systemPackages = [
+            (pkgs.writeShellScriptBin "parent" "")
+          ];
+
+          specialisation.foo = {
+            inheritParentConfig = true;
+
+            configuration = { ... }: {
+              environment.systemPackages = [
+                (pkgs.writeShellScriptBin "foo" "")
+              ];
+            };
+          };
+
+          specialisation.bar = {
+            inheritParentConfig = true;
+
+            configuration = { ... }: {
+              environment.systemPackages = [
+                (pkgs.writeShellScriptBin "bar" "")
+              ];
+            };
+          };
+        }
+      '';
+
+    in
+    ''
+      machine.start()
+      machine.succeed("udevadm settle")
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("nixos-generate-config")
+      machine.copy_from_host(
+          "${configFile}",
+          "/etc/nixos/configuration.nix",
+      )
+
+      with subtest("Switch to the base system"):
+          machine.succeed("nixos-rebuild switch")
+          machine.succeed("parent")
+          machine.fail("foo")
+          machine.fail("bar")
+
+      with subtest("Switch from base system into a specialization"):
+          machine.succeed("nixos-rebuild switch --specialisation foo")
+          machine.succeed("parent")
+          machine.succeed("foo")
+          machine.fail("bar")
+
+      with subtest("Switch from specialization into another specialization"):
+          machine.succeed("nixos-rebuild switch -c bar")
+          machine.succeed("parent")
+          machine.fail("foo")
+          machine.succeed("bar")
+
+      with subtest("Switch from specialization into the base system"):
+          machine.succeed("nixos-rebuild switch")
+          machine.succeed("parent")
+          machine.fail("foo")
+          machine.fail("bar")
+
+      with subtest("Switch into specialization using `nixos-rebuild test`"):
+          machine.succeed("nixos-rebuild test --specialisation foo")
+          machine.succeed("parent")
+          machine.succeed("foo")
+          machine.fail("bar")
+
+      with subtest("Make sure nonsense command combinations are forbidden"):
+          machine.fail("nixos-rebuild boot --specialisation foo")
+          machine.fail("nixos-rebuild boot -c foo")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/nixos-rebuild-target-host.nix b/nixpkgs/nixos/tests/nixos-rebuild-target-host.nix
new file mode 100644
index 000000000000..bf80b2fa6606
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-rebuild-target-host.nix
@@ -0,0 +1,141 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nixos-rebuild-target-host";
+
+  nodes = {
+    deployer = { lib, ... }: let
+      inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+    in {
+      imports = [ ../modules/profiles/installation-device.nix ];
+
+      nix.settings = {
+        substituters = lib.mkForce [ ];
+        hashed-mirrors = null;
+        connect-timeout = 1;
+      };
+
+      environment.systemPackages = [ pkgs.passh ];
+
+      system.includeBuildDependencies = true;
+
+      virtualisation = {
+        cores = 2;
+        memorySize = 2048;
+      };
+
+      system.build.privateKey = snakeOilPrivateKey;
+      system.build.publicKey = snakeOilPublicKey;
+    };
+
+    target = { nodes, lib, ... }: let
+      targetConfig = {
+        documentation.enable = false;
+        services.openssh.enable = true;
+
+        users.users.root.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
+        users.users.alice.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
+        users.users.bob.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
+
+        users.users.alice.extraGroups = [ "wheel" ];
+        users.users.bob.extraGroups = [ "wheel" ];
+
+        # Disable sudo for root to ensure sudo isn't called without `--use-remote-sudo`
+        security.sudo.extraRules = lib.mkForce [
+          { groups = [ "wheel" ]; commands = [ { command = "ALL"; } ]; }
+          { users = [ "alice" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
+        ];
+
+        nix.settings.trusted-users = [ "@wheel" ];
+      };
+    in {
+      imports = [ ./common/user-account.nix ];
+
+      config = lib.mkMerge [
+        targetConfig
+        {
+          system.build = {
+            inherit targetConfig;
+          };
+
+          networking.hostName = "target";
+        }
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      sshConfig = builtins.toFile "ssh.conf" ''
+        UserKnownHostsFile=/dev/null
+        StrictHostKeyChecking=no
+      '';
+
+      targetConfigJSON = pkgs.writeText "target-configuration.json"
+        (builtins.toJSON nodes.target.system.build.targetConfig);
+
+      targetNetworkJSON = pkgs.writeText "target-network.json"
+        (builtins.toJSON nodes.target.system.build.networkConfig);
+
+      configFile = hostname: pkgs.writeText "configuration.nix" ''
+        { lib, modulesPath, ... }: {
+          imports = [
+            (modulesPath + "/virtualisation/qemu-vm.nix")
+            (modulesPath + "/testing/test-instrumentation.nix")
+            (modulesPath + "/../tests/common/user-account.nix")
+            (lib.modules.importJSON ./target-configuration.json)
+            (lib.modules.importJSON ./target-network.json)
+            ./hardware-configuration.nix
+          ];
+
+          boot.loader.grub = {
+            enable = true;
+            device = "/dev/vda";
+            forceInstall = true;
+          };
+
+          # this will be asserted
+          networking.hostName = "${hostname}";
+        }
+      '';
+    in
+    ''
+      start_all()
+      target.wait_for_open_port(22)
+
+      deployer.wait_until_succeeds("ping -c1 target")
+      deployer.succeed("install -Dm 600 ${nodes.deployer.system.build.privateKey} ~root/.ssh/id_ecdsa")
+      deployer.succeed("install ${sshConfig} ~root/.ssh/config")
+
+      target.succeed("nixos-generate-config")
+      deployer.succeed("scp alice@target:/etc/nixos/hardware-configuration.nix /root/hardware-configuration.nix")
+
+      deployer.copy_from_host("${configFile "config-1-deployed"}", "/root/configuration-1.nix")
+      deployer.copy_from_host("${configFile "config-2-deployed"}", "/root/configuration-2.nix")
+      deployer.copy_from_host("${configFile "config-3-deployed"}", "/root/configuration-3.nix")
+      deployer.copy_from_host("${targetNetworkJSON}", "/root/target-network.json")
+      deployer.copy_from_host("${targetConfigJSON}", "/root/target-configuration.json")
+
+      # Ensure sudo is disabled for root
+      target.fail("sudo true")
+
+      # This test also ensures that sudo is not called without --use-remote-sudo
+      with subtest("Deploy to root@target"):
+        deployer.succeed("nixos-rebuild switch -I nixos-config=/root/configuration-1.nix --target-host root@target &>/dev/console")
+        target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname").rstrip()
+        assert target_hostname == "config-1-deployed", f"{target_hostname=}"
+
+      with subtest("Deploy to alice@target with passwordless sudo"):
+        deployer.succeed("nixos-rebuild switch -I nixos-config=/root/configuration-2.nix --target-host alice@target --use-remote-sudo &>/dev/console")
+        target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname").rstrip()
+        assert target_hostname == "config-2-deployed", f"{target_hostname=}"
+
+      with subtest("Deploy to bob@target with password based sudo"):
+        deployer.succeed("passh -c 3 -C -p ${nodes.target.users.users.bob.password} -P \"\[sudo\] password\" nixos-rebuild switch -I nixos-config=/root/configuration-3.nix --target-host bob@target --use-remote-sudo &>/dev/console")
+        target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname").rstrip()
+        assert target_hostname == "config-3-deployed", f"{target_hostname=}"
+
+      with subtest("Deploy works with very long TMPDIR"):
+        tmp_dir = "/var/folder/veryveryveryveryverylongpathnamethatdoesnotworkwithcontrolpath"
+        deployer.succeed(f"mkdir -p {tmp_dir}")
+        deployer.succeed(f"TMPDIR={tmp_dir} nixos-rebuild switch -I nixos-config=/root/configuration-1.nix --target-host root@target &>/dev/console")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/nixos-test-driver/busybox.nix b/nixpkgs/nixos/tests/nixos-test-driver/busybox.nix
new file mode 100644
index 000000000000..426f4494436e
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-test-driver/busybox.nix
@@ -0,0 +1,16 @@
+{
+  name = "Test that basic tests work when busybox is installed";
+
+  nodes = {
+    machine = ({ pkgs, ... }: {
+      environment.systemPackages = [
+        pkgs.busybox
+      ];
+    });
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/nixos-test-driver/extra-python-packages.nix b/nixpkgs/nixos/tests/nixos-test-driver/extra-python-packages.nix
new file mode 100644
index 000000000000..1146bedd996f
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-test-driver/extra-python-packages.nix
@@ -0,0 +1,13 @@
+import ../make-test-python.nix ({ ... }:
+  {
+    name = "extra-python-packages";
+
+    extraPythonPackages = p: [ p.numpy ];
+
+    nodes = { };
+
+    testScript = ''
+      import numpy as np
+      assert str(np.zeros(4) == "array([0., 0., 0., 0.])")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/nixos-test-driver/lib-extend.nix b/nixpkgs/nixos/tests/nixos-test-driver/lib-extend.nix
new file mode 100644
index 000000000000..4fb7cf494aed
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-test-driver/lib-extend.nix
@@ -0,0 +1,31 @@
+{ pkgs, ... }:
+
+let
+  patchedPkgs = pkgs.extend (new: old: {
+    lib = old.lib.extend (self: super: {
+      sorry_dave = "sorry dave";
+    });
+  });
+
+  testBody = {
+    name = "demo lib overlay";
+
+    nodes = {
+      machine = { lib, ... }: {
+        environment.etc."got-lib-overlay".text = lib.sorry_dave;
+      };
+    };
+
+    # We don't need to run an actual test. Instead we build the `machine` configuration
+    # and call it a day, because that already proves that `lib` is wired up correctly.
+    # See the attrset returned at the bottom of this file.
+    testScript = "";
+  };
+
+  inherit (patchedPkgs.testers) nixosTest runNixOSTest;
+  evaluationNixosTest = nixosTest testBody;
+  evaluationRunNixOSTest = runNixOSTest testBody;
+in {
+  nixosTest = evaluationNixosTest.driver.nodes.machine.system.build.toplevel;
+  runNixOSTest = evaluationRunNixOSTest.driver.nodes.machine.system.build.toplevel;
+}
diff --git a/nixpkgs/nixos/tests/nixos-test-driver/node-name.nix b/nixpkgs/nixos/tests/nixos-test-driver/node-name.nix
new file mode 100644
index 000000000000..31386813a516
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-test-driver/node-name.nix
@@ -0,0 +1,33 @@
+{
+  name = "nixos-test-driver.node-name";
+  nodes = {
+    "ok" = { };
+
+    # Valid node name, but not a great host name.
+    "one_two" = { };
+
+    # Valid node name, good host name
+    "a-b" = { };
+
+    # TODO: would be nice to test these eval failures
+    # Not allowed by lib/testing/network.nix (yet?)
+    # "foo.bar" = { };
+    # Not allowed.
+    # "not ok" = { }; # not ok
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("python vars exist and machines are reachable through test backdoor"):
+      ok.succeed("true")
+      one_two.succeed("true")
+      a_b.succeed("true")
+
+    with subtest("hostname is derived from the node name"):
+      ok.succeed("hostname | tee /dev/stderr | grep '^ok$'")
+      one_two.succeed("hostname | tee /dev/stderr | grep '^onetwo$'")
+      a_b.succeed("hostname | tee /dev/stderr | grep '^a-b$'")
+
+  '';
+}
diff --git a/nixpkgs/nixos/tests/nixos-test-driver/timeout.nix b/nixpkgs/nixos/tests/nixos-test-driver/timeout.nix
new file mode 100644
index 000000000000..29bd85d2498e
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixos-test-driver/timeout.nix
@@ -0,0 +1,15 @@
+{
+  name = "Test that sleep of 6 seconds fails a timeout of 5 seconds";
+  globalTimeout = 5;
+
+  nodes = {
+    machine = ({ pkgs, ... }: {
+    });
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("sleep 6")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/nixseparatedebuginfod.nix b/nixpkgs/nixos/tests/nixseparatedebuginfod.nix
new file mode 100644
index 000000000000..7c192a73c706
--- /dev/null
+++ b/nixpkgs/nixos/tests/nixseparatedebuginfod.nix
@@ -0,0 +1,80 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  secret-key = "key-name:/COlMSRbehSh6YSruJWjL+R0JXQUKuPEn96fIb+pLokEJUjcK/2Gv8Ai96D7JGay5gDeUTx5wdpPgNvum9YtwA==";
+  public-key = "key-name:BCVI3Cv9hr/AIveg+yRmsuYA3lE8ecHaT4Db7pvWLcA=";
+in
+{
+  name = "nixseparatedebuginfod";
+  /* A binary cache with debug info and source for nix */
+  nodes.cache = { pkgs, ... }: {
+    services.nix-serve = {
+      enable = true;
+      secretKeyFile = builtins.toFile "secret-key" secret-key;
+      openFirewall = true;
+    };
+    system.extraDependencies = [
+      pkgs.nix.debug
+      pkgs.nix.src
+      pkgs.sl
+    ];
+  };
+  /* the machine where we need the debuginfo */
+  nodes.machine = {
+    imports = [
+      ../modules/installer/cd-dvd/channel.nix
+    ];
+    services.nixseparatedebuginfod.enable = true;
+    nix.settings = {
+      substituters = lib.mkForce [ "http://cache:5000" ];
+      trusted-public-keys = [ public-key ];
+    };
+    environment.systemPackages = [
+      pkgs.valgrind
+      pkgs.gdb
+      (pkgs.writeShellScriptBin "wait_for_indexation" ''
+        set -x
+        while debuginfod-find debuginfo /run/current-system/sw/bin/nix |& grep 'File too large'; do
+          sleep 1;
+        done
+      '')
+    ];
+  };
+  testScript = ''
+    start_all()
+    cache.wait_for_unit("nix-serve.service")
+    cache.wait_for_open_port(5000)
+    machine.wait_for_unit("nixseparatedebuginfod.service")
+    machine.wait_for_open_port(1949)
+
+    with subtest("show the config to debug the test"):
+      machine.succeed("nix --extra-experimental-features nix-command show-config |& logger")
+      machine.succeed("cat /etc/nix/nix.conf |& logger")
+    with subtest("check that the binary cache works"):
+      machine.succeed("nix-store -r ${pkgs.sl}")
+
+    # nixseparatedebuginfod needs .drv to associate executable -> source
+    # on regular systems this would be provided by nixos-rebuild
+    machine.succeed("nix-instantiate '<nixpkgs>' -A nix")
+
+    machine.succeed("timeout 600 wait_for_indexation")
+
+    # test debuginfod-find
+    machine.succeed("debuginfod-find debuginfo /run/current-system/sw/bin/nix")
+
+    # test that gdb can fetch source
+    out = machine.succeed("gdb /run/current-system/sw/bin/nix --batch -x ${builtins.toFile "commands" ''
+    start
+    l
+    ''}")
+    print(out)
+    assert 'int main(' in out
+
+    # test that valgrind can display location information
+    # this relies on the fact that valgrind complains about nix
+    # libgc helps in this regard, and we also ask valgrind to show leak kinds
+    # which are usually false positives.
+    out = machine.succeed("valgrind --leak-check=full --show-leak-kinds=all nix-env --version 2>&1")
+    print(out)
+    assert 'main.cc' in out
+  '';
+})
diff --git a/nixpkgs/nixos/tests/node-red.nix b/nixpkgs/nixos/tests/node-red.nix
new file mode 100644
index 000000000000..5f5960d68295
--- /dev/null
+++ b/nixpkgs/nixos/tests/node-red.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nodered";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ matthewcroughan ];
+  };
+
+  nodes = {
+    client = { config, pkgs, ... }: {
+      environment.systemPackages = [ pkgs.curl ];
+    };
+    nodered = { config, pkgs, ... }: {
+      services.node-red = {
+        enable = true;
+        openFirewall = true;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    nodered.wait_for_unit("node-red.service")
+    nodered.wait_for_open_port(1880)
+
+    client.wait_for_unit("multi-user.target")
+
+    with subtest("Check that the Node-RED webserver can be reached."):
+        assert "<title>Node-RED</title>" in client.succeed(
+            "curl -sSf http:/nodered:1880/ | grep title"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nomad.nix b/nixpkgs/nixos/tests/nomad.nix
new file mode 100644
index 000000000000..51b11a8fef90
--- /dev/null
+++ b/nixpkgs/nixos/tests/nomad.nix
@@ -0,0 +1,97 @@
+import ./make-test-python.nix (
+  { lib, ... }: {
+    name = "nomad";
+    nodes = {
+      default_server = { pkgs, lib, ... }: {
+        networking = {
+          interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [{
+            address = "192.168.1.1";
+            prefixLength = 16;
+          }];
+        };
+
+        environment.etc."nomad.custom.json".source =
+          (pkgs.formats.json { }).generate "nomad.custom.json" {
+            region = "universe";
+            datacenter = "earth";
+          };
+
+        services.nomad = {
+          enable = true;
+
+          settings = {
+            server = {
+              enabled = true;
+              bootstrap_expect = 1;
+            };
+          };
+
+          extraSettingsPaths = [ "/etc/nomad.custom.json" ];
+          enableDocker = false;
+        };
+      };
+
+      custom_state_dir_server = { pkgs, lib, ... }: {
+        networking = {
+          interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [{
+            address = "192.168.1.1";
+            prefixLength = 16;
+          }];
+        };
+
+        environment.etc."nomad.custom.json".source =
+          (pkgs.formats.json { }).generate "nomad.custom.json" {
+            region = "universe";
+            datacenter = "earth";
+          };
+
+        services.nomad = {
+          enable = true;
+          dropPrivileges = false;
+
+          settings = {
+            data_dir = "/nomad/data/dir";
+            server = {
+              enabled = true;
+              bootstrap_expect = 1;
+            };
+          };
+
+          extraSettingsPaths = [ "/etc/nomad.custom.json" ];
+          enableDocker = false;
+        };
+
+        systemd.services.nomad.serviceConfig.ExecStartPre = "${pkgs.writeShellScript "mk_data_dir" ''
+          set -euxo pipefail
+
+          ${pkgs.coreutils}/bin/mkdir -p /nomad/data/dir
+        ''}";
+      };
+    };
+
+    testScript = ''
+      def test_nomad_server(server):
+          server.wait_for_unit("nomad.service")
+
+          # wait for healthy server
+          server.wait_until_succeeds(
+              "[ $(nomad operator raft list-peers | grep true | wc -l) == 1 ]"
+          )
+
+          # wait for server liveness
+          server.succeed("[ $(nomad server members | grep -o alive | wc -l) == 1 ]")
+
+          # check the region
+          server.succeed("nomad server members | grep -o universe")
+
+          # check the datacenter
+          server.succeed("[ $(nomad server members | grep -o earth | wc -l) == 1 ]")
+
+
+      servers = [default_server, custom_state_dir_server]
+
+      for server in servers:
+          test_nomad_server(server)
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/non-default-filesystems.nix b/nixpkgs/nixos/tests/non-default-filesystems.nix
new file mode 100644
index 000000000000..08a17107dd2f
--- /dev/null
+++ b/nixpkgs/nixos/tests/non-default-filesystems.nix
@@ -0,0 +1,172 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+{
+  bind = makeTest {
+    name = "non-default-filesystem-bind";
+
+    nodes.machine = { ... }: {
+      virtualisation.writableStore = false;
+
+      virtualisation.fileSystems."/test-bind-dir/bind" = {
+        device = "/";
+        neededForBoot = true;
+        options = [ "bind" ];
+      };
+
+      virtualisation.fileSystems."/test-bind-file/bind" = {
+        depends = [ "/nix/store" ];
+        device = builtins.toFile "empty" "";
+        neededForBoot = true;
+        options = [ "bind" ];
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("multi-user.target")
+    '';
+  };
+
+  btrfs = makeTest
+    {
+      name = "non-default-filesystems-btrfs";
+
+      nodes.machine =
+        { config, pkgs, lib, ... }:
+        let
+          disk = config.virtualisation.rootDevice;
+        in
+        {
+          virtualisation.rootDevice = "/dev/vda";
+          virtualisation.useDefaultFilesystems = false;
+
+          boot.initrd.availableKernelModules = [ "btrfs" ];
+          boot.supportedFilesystems = [ "btrfs" ];
+
+          boot.initrd.postDeviceCommands = ''
+            FSTYPE=$(blkid -o value -s TYPE ${disk} || true)
+            if test -z "$FSTYPE"; then
+              modprobe btrfs
+              ${pkgs.btrfs-progs}/bin/mkfs.btrfs ${disk}
+
+              mkdir /nixos
+              mount -t btrfs ${disk} /nixos
+
+              ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/root
+              ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/home
+
+              umount /nixos
+            fi
+          '';
+
+          virtualisation.fileSystems = {
+            "/" = {
+              device = disk;
+              fsType = "btrfs";
+              options = [ "subvol=/root" ];
+            };
+
+            "/home" = {
+              device = disk;
+              fsType = "btrfs";
+              options = [ "subvol=/home" ];
+            };
+          };
+        };
+
+      testScript = ''
+        machine.wait_for_unit("multi-user.target")
+
+        with subtest("BTRFS filesystems are mounted correctly"):
+          machine.succeed("grep -E '/dev/vda / btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts")
+          machine.succeed("grep -E '/dev/vda /home btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts")
+      '';
+    };
+
+  erofs =
+    let
+      fsImage = "/tmp/non-default-filesystem.img";
+    in
+    makeTest {
+      name = "non-default-filesystems-erofs";
+
+      meta.maintainers = with maintainers; [ nikstur ];
+
+      nodes.machine = _: {
+        virtualisation.qemu.drives = [{
+          name = "non-default-filesystem";
+          file = fsImage;
+        }];
+
+        virtualisation.fileSystems."/non-default" = {
+          device = "/dev/vdb";
+          fsType = "erofs";
+          neededForBoot = true;
+        };
+      };
+
+      testScript = ''
+        import subprocess
+        import tempfile
+
+        with tempfile.TemporaryDirectory() as tmp_dir:
+          with open(f"{tmp_dir}/filesystem", "w") as f:
+              f.write("erofs")
+
+          subprocess.run([
+            "${pkgs.erofs-utils}/bin/mkfs.erofs",
+            "${fsImage}",
+            tmp_dir,
+          ])
+
+        machine.start()
+        machine.wait_for_unit("default.target")
+
+        file_contents = machine.succeed("cat /non-default/filesystem")
+        assert "erofs" in file_contents
+      '';
+    };
+
+  squashfs =
+    let
+      fsImage = "/tmp/non-default-filesystem.img";
+    in
+    makeTest {
+      name = "non-default-filesystems-squashfs";
+
+      meta.maintainers = with maintainers; [ nikstur ];
+
+      nodes.machine = {
+        virtualisation.qemu.drives = [{
+          name = "non-default-filesystem";
+          file = fsImage;
+          deviceExtraOpts.serial = "non-default";
+        }];
+
+        virtualisation.fileSystems."/non-default" = {
+          device = "/dev/disk/by-id/virtio-non-default";
+          fsType = "squashfs";
+          neededForBoot = true;
+        };
+      };
+
+      testScript = ''
+        import subprocess
+
+        with open("filesystem", "w") as f:
+          f.write("squashfs")
+
+        subprocess.run([
+          "${pkgs.squashfsTools}/bin/mksquashfs",
+          "filesystem",
+          "${fsImage}",
+        ])
+
+        assert "squashfs" in machine.succeed("cat /non-default/filesystem")
+      '';
+    };
+}
diff --git a/nixpkgs/nixos/tests/non-switchable-system.nix b/nixpkgs/nixos/tests/non-switchable-system.nix
new file mode 100644
index 000000000000..54bede75453b
--- /dev/null
+++ b/nixpkgs/nixos/tests/non-switchable-system.nix
@@ -0,0 +1,15 @@
+{ lib, ... }:
+
+{
+  name = "non-switchable-system";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = {
+    system.switch.enable = false;
+  };
+
+  testScript = ''
+    machine.succeed("test ! -e /run/current-system/bin/switch-to-configuration")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/noto-fonts-cjk-qt-default-weight.nix b/nixpkgs/nixos/tests/noto-fonts-cjk-qt-default-weight.nix
new file mode 100644
index 000000000000..c2e0cb3adaeb
--- /dev/null
+++ b/nixpkgs/nixos/tests/noto-fonts-cjk-qt-default-weight.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "noto-fonts-cjk-qt";
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine = {
+    imports = [ ./common/x11.nix ];
+    fonts = {
+      enableDefaultPackages = false;
+      fonts = [ pkgs.noto-fonts-cjk-sans ];
+    };
+  };
+
+  testScript =
+    let
+      script = pkgs.writers.writePython3 "qt-default-weight" {
+        libraries = [ pkgs.python3Packages.pyqt6 ];
+      } ''
+        from PyQt6.QtWidgets import QApplication
+        from PyQt6.QtGui import QFont, QRawFont
+
+        app = QApplication([])
+        f = QRawFont.fromFont(QFont("Noto Sans CJK SC", 20))
+
+        assert f.styleName() == "Regular", f.styleName()
+      '';
+    in ''
+      machine.wait_for_x()
+      machine.succeed("${script}")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/noto-fonts.nix b/nixpkgs/nixos/tests/noto-fonts.nix
new file mode 100644
index 000000000000..b871f5f51729
--- /dev/null
+++ b/nixpkgs/nixos/tests/noto-fonts.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "noto-fonts";
+  meta.maintainers = with lib.maintainers; [ nickcao midchildan ];
+
+  nodes.machine = {
+    imports = [ ./common/x11.nix ];
+    environment.systemPackages = [ pkgs.gedit ];
+    fonts = {
+      enableDefaultPackages = false;
+      fonts = with pkgs;[
+        noto-fonts
+        noto-fonts-cjk-sans
+        noto-fonts-cjk-serif
+        noto-fonts-color-emoji
+      ];
+      fontconfig.defaultFonts = {
+        serif = [ "Noto Serif" "Noto Serif CJK SC" ];
+        sansSerif = [ "Noto Sans" "Noto Sans CJK SC" ];
+        monospace = [ "Noto Sans Mono" "Noto Sans Mono CJK SC" ];
+        emoji = [ "Noto Color Emoji" ];
+      };
+    };
+  };
+
+  testScript =
+    # extracted from http://www.clagnut.com/blog/2380/
+    let testText = builtins.toFile "test.txt" ''
+      the quick brown fox jumps over the lazy dog
+      視野無é™å»£ï¼Œçª—外有è—天
+      EÄ¥oÅanÄo ĉiuĵaÅ­de.
+      ã„ã‚ã¯ã«ã»ã¸ã¨ ã¡ã‚Šã¬ã‚‹ã‚’ ã‚ã‹ã‚ˆãŸã‚Œã ã¤ã­ãªã‚‰ã‚€ ã†ã‚ã®ãŠãã‚„ã¾ ã‘ãµã“ãˆã¦ ã‚ã•ãゆã‚ã¿ã— ã‚‘ã²ã‚‚ã›ã™
+      ë‹¤ëžŒì¥ í—Œ ì³‡ë°”í€´ì— íƒ€ê³ íŒŒ
+      中国智造,慧åŠå…¨çƒ
+    ''; in
+    ''
+      machine.wait_for_x()
+      machine.succeed("gedit ${testText} >&2 &")
+      machine.wait_for_window(".* - gedit")
+      machine.sleep(10)
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/novacomd.nix b/nixpkgs/nixos/tests/novacomd.nix
new file mode 100644
index 000000000000..d47d212fb2ec
--- /dev/null
+++ b/nixpkgs/nixos/tests/novacomd.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "novacomd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ dtzWill ];
+  };
+
+  nodes.machine = { ... }: {
+    services.novacomd.enable = true;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("novacomd.service")
+
+    with subtest("Make sure the daemon is really listening"):
+        machine.wait_for_open_port(6968)
+        machine.succeed("novacom -l")
+
+    with subtest("Stop the daemon, double-check novacom fails if daemon isn't working"):
+        machine.stop_job("novacomd")
+        machine.fail("novacom -l")
+
+    with subtest("Make sure the daemon starts back up again"):
+        machine.start_job("novacomd")
+        # make sure the daemon is really listening
+        machine.wait_for_open_port(6968)
+        machine.succeed("novacom -l")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/npmrc.nix b/nixpkgs/nixos/tests/npmrc.nix
new file mode 100644
index 000000000000..dbf24d372feb
--- /dev/null
+++ b/nixpkgs/nixos/tests/npmrc.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ ... }:
+let
+  machineName = "machine";
+  settingName = "prefix";
+  settingValue = "/some/path";
+in
+{
+  name = "npmrc";
+
+  nodes."${machineName}".programs.npm = {
+    enable = true;
+    npmrc = ''
+      ${settingName} = ${settingValue}
+    '';
+  };
+
+  testScript = ''
+    ${machineName}.start()
+
+    assert ${machineName}.succeed("npm config get ${settingName}") == "${settingValue}\n"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nscd.nix b/nixpkgs/nixos/tests/nscd.nix
new file mode 100644
index 000000000000..356c6d2e2a54
--- /dev/null
+++ b/nixpkgs/nixos/tests/nscd.nix
@@ -0,0 +1,142 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  # build a getent that itself doesn't see anything in /etc/hosts and
+  # /etc/nsswitch.conf, by using libredirect to steer its own requests to
+  # /dev/null.
+  # This means is /has/ to go via nscd to actuallly resolve any of the
+  # additionally configured hosts.
+  getent' = pkgs.writeScript "getent-without-etc-hosts" ''
+    export NIX_REDIRECTS=/etc/hosts=/dev/null:/etc/nsswitch.conf=/dev/null
+    export LD_PRELOAD=${pkgs.libredirect}/lib/libredirect.so
+    exec getent $@
+  '';
+in
+{
+  name = "nscd";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    networking.extraHosts = ''
+      2001:db8::1 somehost.test
+      192.0.2.1 somehost.test
+    '';
+
+    systemd.services.sockdump = {
+      wantedBy = [ "multi-user.target" ];
+      path = [
+        # necessary for bcc to unpack kernel headers and invoke modprobe
+        pkgs.gnutar
+        pkgs.xz.bin
+        pkgs.kmod
+      ];
+      environment.PYTHONUNBUFFERED = "1";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.sockdump}/bin/sockdump /var/run/nscd/socket";
+        Restart = "on-failure";
+        RestartSec = "1";
+        Type = "simple";
+      };
+    };
+
+    specialisation = {
+      withGlibcNscd.configuration = { ... }: {
+        services.nscd.enableNsncd = false;
+      };
+      withUnscd.configuration = { ... }: {
+        services.nscd.enableNsncd = false;
+        services.nscd.package = pkgs.unscd;
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+    in
+    ''
+      # Regression test for https://github.com/NixOS/nixpkgs/issues/50273
+      def test_dynamic_user():
+          with subtest("DynamicUser actually allocates a user"):
+              assert "iamatest" in machine.succeed(
+                  "systemd-run --pty --property=Type=oneshot --property=DynamicUser=yes --property=User=iamatest whoami"
+              )
+
+      # Test resolution of somehost.test with getent', to make sure we go via
+      # nscd protocol
+      def test_host_lookups():
+          with subtest("host lookups via nscd protocol"):
+              # ahosts
+              output = machine.succeed("${getent'} ahosts somehost.test")
+              assert "192.0.2.1" in output
+              assert "2001:db8::1" in output
+
+              # ahostsv4
+              output = machine.succeed("${getent'} ahostsv4 somehost.test")
+              assert "192.0.2.1" in output
+              assert "2001:db8::1" not in output
+
+              # ahostsv6
+              output = machine.succeed("${getent'} ahostsv6 somehost.test")
+              assert "192.0.2.1" not in output
+              assert "2001:db8::1" in output
+
+              # reverse lookups (hosts)
+              assert "somehost.test" in machine.succeed("${getent'} hosts 2001:db8::1")
+              assert "somehost.test" in machine.succeed("${getent'} hosts 192.0.2.1")
+
+
+      # Test host resolution via nss modules works
+      # We rely on nss-myhostname in this case, which resolves *.localhost and
+      # _gateway.
+      # We don't need to use getent' here, as non-glibc nss modules can only be
+      # discovered via nscd.
+      def test_nss_myhostname():
+          with subtest("nss-myhostname provides hostnames (ahosts)"):
+              # ahosts
+              output = machine.succeed("getent ahosts foobar.localhost")
+              assert "::1" in output
+              assert "127.0.0.1" in output
+
+              # ahostsv4
+              output = machine.succeed("getent ahostsv4 foobar.localhost")
+              assert "::1" not in output
+              assert "127.0.0.1" in output
+
+              # ahostsv6
+              output = machine.succeed("getent ahostsv6 foobar.localhost")
+              assert "::1" in output
+              assert "127.0.0.1" not in output
+
+      start_all()
+      machine.wait_for_unit("default.target")
+
+      # give sockdump some time to finish attaching.
+      machine.sleep(5)
+
+      # Test all tests with glibc-nscd.
+      test_dynamic_user()
+      test_host_lookups()
+      test_nss_myhostname()
+
+      with subtest("glibc-nscd"):
+          machine.succeed('${specialisations}/withGlibcNscd/bin/switch-to-configuration test')
+          machine.wait_for_unit("default.target")
+
+          test_dynamic_user()
+          test_host_lookups()
+          test_nss_myhostname()
+
+      with subtest("unscd"):
+          machine.succeed('${specialisations}/withUnscd/bin/switch-to-configuration test')
+          machine.wait_for_unit("default.target")
+
+          # known to fail, unscd doesn't load external NSS modules
+          # test_dynamic_user()
+
+          test_host_lookups()
+
+          # known to fail, unscd doesn't load external NSS modules
+          # test_nss_myhostname()
+    '';
+})
diff --git a/nixpkgs/nixos/tests/nsd.nix b/nixpkgs/nixos/tests/nsd.nix
new file mode 100644
index 000000000000..eea5a82f6f92
--- /dev/null
+++ b/nixpkgs/nixos/tests/nsd.nix
@@ -0,0 +1,109 @@
+let
+  common = { pkgs, ... }: {
+    networking.firewall.enable = false;
+    networking.useDHCP = false;
+    # for a host utility with IPv6 support
+    environment.systemPackages = [ pkgs.bind ];
+  };
+in import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "nsd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ aszlig ];
+  };
+
+  nodes = {
+    clientv4 = { lib, nodes, ... }: {
+      imports = [ common ];
+      networking.nameservers = lib.mkForce [
+        (lib.head nodes.server.config.networking.interfaces.eth1.ipv4.addresses).address
+      ];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "192.168.0.2"; prefixLength = 24; }
+      ];
+    };
+
+    clientv6 = { lib, nodes, ... }: {
+      imports = [ common ];
+      networking.nameservers = lib.mkForce [
+        (lib.head nodes.server.config.networking.interfaces.eth1.ipv6.addresses).address
+      ];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "dead:beef::2"; prefixLength = 24; }
+      ];
+    };
+
+    server = { lib, ... }: {
+      imports = [ common ];
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "192.168.0.1"; prefixLength = 24; }
+      ];
+      networking.interfaces.eth1.ipv6.addresses = [
+        { address = "dead:beef::1"; prefixLength = 64; }
+      ];
+      services.nsd.enable = true;
+      services.nsd.rootServer = true;
+      services.nsd.interfaces = lib.mkForce [];
+      services.nsd.keys."tsig.example.com." = {
+        algorithm = "hmac-sha256";
+        keyFile = pkgs.writeTextFile { name = "tsig.example.com."; text = "aR3FJA92+bxRSyosadsJ8Aeeav5TngQW/H/EF9veXbc="; };
+      };
+      services.nsd.zones."example.com.".data = ''
+        @ SOA ns.example.com noc.example.com 666 7200 3600 1209600 3600
+        ipv4 A 1.2.3.4
+        ipv6 AAAA abcd::eeff
+        deleg NS ns.example.com
+        ns A 192.168.0.1
+        ns AAAA dead:beef::1
+      '';
+      services.nsd.zones."example.com.".provideXFR = [ "0.0.0.0 tsig.example.com." ];
+      services.nsd.zones."deleg.example.com.".data = ''
+        @ SOA ns.example.com noc.example.com 666 7200 3600 1209600 3600
+        @ A 9.8.7.6
+        @ AAAA fedc::bbaa
+      '';
+      services.nsd.zones.".".data = ''
+        @ SOA ns.example.com noc.example.com 666 7200 3600 1209600 3600
+        root A 1.8.7.4
+        root AAAA acbd::4
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    clientv4.wait_for_unit("network.target")
+    clientv6.wait_for_unit("network.target")
+    server.wait_for_unit("nsd.service")
+
+    with subtest("server tsig.example.com."):
+        expected_tsig = "  secret: \"aR3FJA92+bxRSyosadsJ8Aeeav5TngQW/H/EF9veXbc=\"\n"
+        tsig=server.succeed("cat /var/lib/nsd/private/tsig.example.com.")
+        assert expected_tsig == tsig, f"Expected /var/lib/nsd/private/tsig.example.com. to contain '{expected_tsig}', but found '{tsig}'"
+
+    def assert_host(type, rr, query, expected):
+        self = clientv4 if type == 4 else clientv6
+        out = self.succeed(f"host -{type} -t {rr} {query}").rstrip()
+        self.log(f"output: {out}")
+        import re
+        assert re.search(
+            expected, out
+        ), f"DNS IPv{type} query on {query} gave '{out}' instead of '{expected}'"
+
+
+    for ipv in 4, 6:
+        with subtest(f"IPv{ipv}"):
+            assert_host(ipv, "a", "example.com", "has no [^ ]+ record")
+            assert_host(ipv, "aaaa", "example.com", "has no [^ ]+ record")
+
+            assert_host(ipv, "soa", "example.com", "SOA.*?noc\.example\.com")
+            assert_host(ipv, "a", "ipv4.example.com", "address 1.2.3.4$")
+            assert_host(ipv, "aaaa", "ipv6.example.com", "address abcd::eeff$")
+
+            assert_host(ipv, "a", "deleg.example.com", "address 9.8.7.6$")
+            assert_host(ipv, "aaaa", "deleg.example.com", "address fedc::bbaa$")
+
+            assert_host(ipv, "a", "root", "address 1.8.7.4$")
+            assert_host(ipv, "aaaa", "root", "address acbd::4$")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ntfy-sh-migration.nix b/nixpkgs/nixos/tests/ntfy-sh-migration.nix
new file mode 100644
index 000000000000..de6660052d67
--- /dev/null
+++ b/nixpkgs/nixos/tests/ntfy-sh-migration.nix
@@ -0,0 +1,77 @@
+# the ntfy-sh module was switching to DynamicUser=true. this test assures that
+# the migration does not break existing setups.
+#
+# this test works doing a migration and asserting ntfy-sh runs properly. first,
+# ntfy-sh is configured to use a static user and group. then ntfy-sh is
+# started and tested. after that, ntfy-sh is shut down and a systemd drop
+# in configuration file is used to upate the service configuration to use
+# DynamicUser=true. then the ntfy-sh is started again and tested.
+
+import ./make-test-python.nix {
+  name = "ntfy-sh";
+
+  nodes.machine = {
+    lib,
+    pkgs,
+    ...
+  }: {
+    environment.etc."ntfy-sh-dynamic-user.conf".text = ''
+      [Service]
+      Group=new-ntfy-sh
+      User=new-ntfy-sh
+      DynamicUser=true
+    '';
+
+    services.ntfy-sh.enable = true;
+    services.ntfy-sh.settings.base-url = "http://localhost:2586";
+
+    systemd.services.ntfy-sh.serviceConfig = {
+      DynamicUser = lib.mkForce false;
+      ExecStartPre = [
+        "${pkgs.coreutils}/bin/id"
+        "${pkgs.coreutils}/bin/ls -lahd /var/lib/ntfy-sh/"
+        "${pkgs.coreutils}/bin/ls -lah /var/lib/ntfy-sh/"
+      ];
+      Group = lib.mkForce "old-ntfy-sh";
+      User = lib.mkForce "old-ntfy-sh";
+    };
+
+    users.users.old-ntfy-sh = {
+      isSystemUser = true;
+      group = "old-ntfy-sh";
+    };
+
+    users.groups.old-ntfy-sh = {};
+  };
+
+  testScript = ''
+    import json
+
+    msg = "Test notification"
+
+    def test_ntfysh():
+      machine.wait_for_unit("ntfy-sh.service")
+      machine.wait_for_open_port(2586)
+
+      machine.succeed(f"curl -d '{msg}' localhost:2586/test")
+
+      text = machine.succeed("curl -s localhost:2586/test/json?poll=1")
+      for line in text.splitlines():
+        notif = json.loads(line)
+        assert msg == notif["message"], "Wrong message"
+
+      machine.succeed("ntfy user list")
+
+    machine.wait_for_unit("multi-user.target")
+
+    test_ntfysh()
+
+    machine.succeed("systemctl stop ntfy-sh.service")
+    machine.succeed("mkdir -p /run/systemd/system/ntfy-sh.service.d")
+    machine.succeed("cp /etc/ntfy-sh-dynamic-user.conf /run/systemd/system/ntfy-sh.service.d/dynamic-user.conf")
+    machine.succeed("systemctl daemon-reload")
+    machine.succeed("systemctl start ntfy-sh.service")
+
+    test_ntfysh()
+  '';
+}
diff --git a/nixpkgs/nixos/tests/ntfy-sh.nix b/nixpkgs/nixos/tests/ntfy-sh.nix
new file mode 100644
index 000000000000..ec2e645bacb5
--- /dev/null
+++ b/nixpkgs/nixos/tests/ntfy-sh.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix {
+  name = "ntfy-sh";
+
+  nodes.machine = { ... }: {
+    services.ntfy-sh.enable = true;
+    services.ntfy-sh.settings.base-url = "http://localhost:2586";
+  };
+
+  testScript = ''
+    import json
+
+    msg = "Test notification"
+
+    machine.wait_for_unit("multi-user.target")
+
+    machine.wait_for_open_port(2586)
+
+    machine.succeed(f"curl -d '{msg}' localhost:2586/test")
+
+    notif = json.loads(machine.succeed("curl -s localhost:2586/test/json?poll=1"))
+
+    assert msg == notif["message"], "Wrong message"
+
+    machine.succeed("ntfy user list")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/ntpd-rs.nix b/nixpkgs/nixos/tests/ntpd-rs.nix
new file mode 100644
index 000000000000..6f3c80e87f07
--- /dev/null
+++ b/nixpkgs/nixos/tests/ntpd-rs.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "ntpd-rs";
+
+  meta = {
+    maintainers = with lib.maintainers; [ fpletz ];
+  };
+
+  nodes = {
+    client = {
+      services.ntpd-rs = {
+        enable = true;
+        metrics.enable = true;
+        useNetworkingTimeServers = false;
+        settings = {
+          source = [
+            {
+              mode = "server";
+              address = "server";
+            }
+          ];
+          synchronization = {
+            minimum-agreeing-sources = 1;
+          };
+        };
+      };
+    };
+    server = {
+      networking.firewall.allowedUDPPorts = [ 123 ];
+      services.ntpd-rs = {
+        enable = true;
+        metrics.enable = true;
+        settings = {
+          server = [
+            { listen = "[::]:123"; }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    for machine in (server, client):
+      machine.wait_for_unit('multi-user.target')
+      machine.succeed('systemctl is-active ntpd-rs.service')
+      machine.succeed('systemctl is-active ntpd-rs-metrics.service')
+      machine.succeed('curl http://localhost:9975/metrics | grep ntp_uptime_seconds')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nvmetcfg.nix b/nixpkgs/nixos/tests/nvmetcfg.nix
new file mode 100644
index 000000000000..a4c459a343cf
--- /dev/null
+++ b/nixpkgs/nixos/tests/nvmetcfg.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "nvmetcfg";
+
+  meta = {
+    maintainers = with lib.maintainers; [ nickcao ];
+  };
+
+  nodes = {
+    server = { pkgs, ... }: {
+      boot.kernelModules = [ "nvmet" ];
+      environment.systemPackages = [ pkgs.nvmetcfg ];
+      networking.firewall.allowedTCPPorts = [ 4420 ];
+      virtualisation.emptyDiskImages = [ 512 ];
+    };
+    client = { pkgs, ... }: {
+      boot.kernelModules = [ "nvme-fabrics" ];
+      environment.systemPackages = [ pkgs.nvme-cli ];
+    };
+  };
+
+  testScript = let subsystem = "nqn.2014-08.org.nixos:server"; in ''
+    import json
+
+    with subtest("Create subsystem and namespace"):
+      server.succeed("nvmet subsystem add ${subsystem}")
+      server.succeed("nvmet namespace add ${subsystem} 1 /dev/vdb")
+
+    with subtest("Bind subsystem to port"):
+      server.wait_for_unit("network-online.target")
+      server.succeed("nvmet port add 1 tcp 0.0.0.0:4420")
+      server.succeed("nvmet port add-subsystem 1 ${subsystem}")
+
+    with subtest("Discover and connect to available subsystems"):
+      client.wait_for_unit("network-online.target")
+      assert "subnqn:  ${subsystem}" in client.succeed("nvme discover --transport=tcp --traddr=server --trsvcid=4420")
+      client.succeed("nvme connect-all --transport=tcp --traddr=server --trsvcid=4420")
+
+    with subtest("Write to the connected subsystem"):
+      devices = json.loads(client.succeed("lsblk --nvme --paths --json"))["blockdevices"]
+      assert len(devices) == 1
+      client.succeed(f"dd if=/dev/zero of={devices[0]['name']} bs=1M count=64")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nzbget.nix b/nixpkgs/nixos/tests/nzbget.nix
new file mode 100644
index 000000000000..e45031d5629c
--- /dev/null
+++ b/nixpkgs/nixos/tests/nzbget.nix
@@ -0,0 +1,46 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "nzbget";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ aanderse flokli ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      services.nzbget.enable = true;
+
+      # provide some test settings
+      services.nzbget.settings = {
+        "MainDir" = "/var/lib/nzbget";
+        "DirectRename" = true;
+        "DiskSpace" = 0;
+        "Server1.Name" = "this is a test";
+      };
+
+      # hack, don't add (unfree) unrar to nzbget's path,
+      # so we can run this test in CI
+      systemd.services.nzbget.path = pkgs.lib.mkForce [ pkgs.p7zip ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    server.wait_for_unit("nzbget.service")
+    server.wait_for_unit("network.target")
+    server.wait_for_open_port(6789)
+    assert "This file is part of nzbget" in server.succeed(
+        "curl -f -s -u nzbget:tegbzn6789 http://127.0.0.1:6789"
+    )
+    server.succeed(
+        "${pkgs.nzbget}/bin/nzbget -n -o Control_iP=127.0.0.1 -o Control_port=6789 -o Control_password=tegbzn6789 -V"
+    )
+
+    config = server.succeed("${nodes.server.systemd.services.nzbget.serviceConfig.ExecStart} --printconfig")
+
+    # confirm the test settings are applied
+    assert 'MainDir = "/var/lib/nzbget"' in config
+    assert 'DirectRename = "yes"' in config
+    assert 'DiskSpace = "0"' in config
+    assert 'Server1.Name = "this is a test"' in config
+  '';
+})
diff --git a/nixpkgs/nixos/tests/nzbhydra2.nix b/nixpkgs/nixos/tests/nzbhydra2.nix
new file mode 100644
index 000000000000..e1d528cd9520
--- /dev/null
+++ b/nixpkgs/nixos/tests/nzbhydra2.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ lib, ... }:
+  {
+    name = "nzbhydra2";
+    meta.maintainers = with lib.maintainers; [ jamiemagee ];
+
+    nodes.machine = { pkgs, ... }: { services.nzbhydra2.enable = true; };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("nzbhydra2.service")
+      machine.wait_for_open_port(5076)
+      machine.succeed("curl --fail http://localhost:5076/")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/oci-containers.nix b/nixpkgs/nixos/tests/oci-containers.nix
new file mode 100644
index 000000000000..1f8e276204a8
--- /dev/null
+++ b/nixpkgs/nixos/tests/oci-containers.nix
@@ -0,0 +1,47 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+}:
+
+let
+
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+
+  mkOCITest = backend: makeTest {
+    name = "oci-containers-${backend}";
+
+    meta.maintainers = lib.teams.serokell.members
+                       ++ (with lib.maintainers; [ benley mkaito ]);
+
+    nodes = {
+      ${backend} = { pkgs, ... }: {
+        virtualisation.oci-containers = {
+          inherit backend;
+          containers.nginx = {
+            image = "nginx-container";
+            imageFile = pkgs.dockerTools.examples.nginx;
+            ports = ["8181:80"];
+          };
+        };
+
+        # Stop systemd from killing remaining processes if ExecStop script
+        # doesn't work, so that proper stopping can be tested.
+        systemd.services."${backend}-nginx".serviceConfig.KillSignal = "SIGCONT";
+      };
+    };
+
+    testScript = ''
+      start_all()
+      ${backend}.wait_for_unit("${backend}-nginx.service")
+      ${backend}.wait_for_open_port(8181)
+      ${backend}.wait_until_succeeds("curl -f http://localhost:8181 | grep Hello")
+      ${backend}.succeed("systemctl stop ${backend}-nginx.service", timeout=10)
+    '';
+  };
+
+in
+lib.foldl' (attrs: backend: attrs // { ${backend} = mkOCITest backend; }) {} [
+  "docker"
+  "podman"
+]
diff --git a/nixpkgs/nixos/tests/ocsinventory-agent.nix b/nixpkgs/nixos/tests/ocsinventory-agent.nix
new file mode 100644
index 000000000000..67b0c8c91103
--- /dev/null
+++ b/nixpkgs/nixos/tests/ocsinventory-agent.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "ocsinventory-agent";
+
+  nodes.machine = { pkgs, ... }: {
+    services.ocsinventory-agent = {
+      enable = true;
+      settings = {
+        debug = true;
+        local = "/var/lib/ocsinventory-agent/reports";
+        tag = "MY_INVENTORY_TAG";
+      };
+    };
+  };
+
+  testScript = ''
+    path = "/var/lib/ocsinventory-agent/reports"
+
+    # Run the agent to generate the inventory file in offline mode
+    start_all()
+    machine.succeed("mkdir -p {}".format(path))
+    machine.wait_for_unit("ocsinventory-agent.service")
+    machine.wait_until_succeeds("journalctl -u ocsinventory-agent.service | grep 'Inventory saved in'")
+
+    # Fetch the path to the generated inventory file
+    report_file = machine.succeed("find {}/*.ocs -type f | head -n1".format(path))
+
+    with subtest("Check the tag value"):
+      tag = machine.succeed(
+        "${pkgs.libxml2}/bin/xmllint --xpath 'string(/REQUEST/CONTENT/ACCOUNTINFO/KEYVALUE)' {}".format(report_file)
+      ).rstrip()
+      assert tag == "MY_INVENTORY_TAG", f"tag is not valid, was '{tag}'"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/octoprint.nix b/nixpkgs/nixos/tests/octoprint.nix
new file mode 100644
index 000000000000..15a2d677d4cf
--- /dev/null
+++ b/nixpkgs/nixos/tests/octoprint.nix
@@ -0,0 +1,61 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  apikey = "testapikey";
+in
+{
+  name = "octoprint";
+  meta.maintainers = with lib.maintainers; [ gador ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ jq ];
+    services.octoprint = {
+      enable = true;
+      extraConfig = {
+        server = {
+          firstRun = false;
+        };
+        api = {
+          enabled = true;
+          key = apikey;
+        };
+        plugins = {
+          # these need internet access and pollute the output with connection failed errors
+          _disabled = [ "softwareupdate" "announcements" "pluginmanager" ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    @polling_condition
+    def octoprint_running():
+        machine.succeed("pgrep octoprint")
+
+    with subtest("Wait for octoprint service to start"):
+        machine.wait_for_unit("octoprint.service")
+        machine.wait_until_succeeds("pgrep octoprint")
+
+    with subtest("Wait for final boot"):
+        # this appears whe octoprint is almost finished starting
+        machine.wait_for_file("/var/lib/octoprint/uploads")
+
+    # octoprint takes some time to start. This makes sure we'll retry just in case it takes longer
+    # retry-all-errors in necessary, since octoprint will report a 404 error when not yet ready
+    curl_cmd = "curl --retry-all-errors --connect-timeout 5 --max-time 10 --retry 5 --retry-delay 0 \
+                --retry-max-time 40 -X GET --header 'X-API-Key: ${apikey}' "
+
+    # used to fail early, in case octoprint first starts and then crashes
+    with octoprint_running: # type: ignore[union-attr]
+        with subtest("Check for web interface"):
+            machine.wait_until_succeeds("curl -s localhost:5000")
+
+        with subtest("Check API"):
+            version = json.loads(machine.succeed(curl_cmd + "localhost:5000/api/version"))
+            server = json.loads(machine.succeed(curl_cmd + "localhost:5000/api/server"))
+            assert version["server"] == str("${pkgs.octoprint.version}")
+            assert server["safemode"] == None
+  '';
+})
diff --git a/nixpkgs/nixos/tests/odoo.nix b/nixpkgs/nixos/tests/odoo.nix
new file mode 100644
index 000000000000..00ae4a2137d1
--- /dev/null
+++ b/nixpkgs/nixos/tests/odoo.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ pkgs, lib, package ? pkgs.odoo, ...} : {
+  name = "odoo";
+  meta.maintainers = with lib.maintainers; [ mkg20001 ];
+
+  nodes = {
+    server = { ... }: {
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+      };
+
+      services.odoo = {
+        enable = true;
+        package = package;
+        domain = "localhost";
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+  ''
+    server.wait_for_unit("odoo.service")
+    server.wait_until_succeeds("curl -s http://localhost:8069/web/database/selector | grep '<title>Odoo</title>'")
+    server.succeed("curl -s http://localhost/web/database/selector | grep '<title>Odoo</title>'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/oh-my-zsh.nix b/nixpkgs/nixos/tests/oh-my-zsh.nix
new file mode 100644
index 000000000000..1d5227e36236
--- /dev/null
+++ b/nixpkgs/nixos/tests/oh-my-zsh.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "oh-my-zsh";
+
+  nodes.machine = { pkgs, ... }:
+
+    {
+      programs.zsh = {
+        enable = true;
+        ohMyZsh.enable = true;
+      };
+    };
+
+  testScript = ''
+    start_all()
+    machine.succeed("touch ~/.zshrc")
+    machine.succeed("zsh -c 'source /etc/zshrc && echo $ZSH | grep oh-my-zsh-${pkgs.oh-my-zsh.version}'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ombi.nix b/nixpkgs/nixos/tests/ombi.nix
new file mode 100644
index 000000000000..fb3a37c978e3
--- /dev/null
+++ b/nixpkgs/nixos/tests/ombi.nix
@@ -0,0 +1,16 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "ombi";
+  meta.maintainers = with lib.maintainers; [ woky ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.ombi.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("ombi.service")
+    machine.wait_for_open_port(5000)
+    machine.succeed("curl --fail http://localhost:5000/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/openarena.nix b/nixpkgs/nixos/tests/openarena.nix
new file mode 100644
index 000000000000..63dc1b9a6857
--- /dev/null
+++ b/nixpkgs/nixos/tests/openarena.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      hardware.opengl.driSupport = true;
+      environment.systemPackages = [ pkgs.openarena ];
+    };
+
+in {
+  name = "openarena";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fpletz ];
+  };
+
+  nodes =
+    { server =
+        { services.openarena = {
+            enable = true;
+            extraFlags = [ "+set g_gametype 0" "+map oa_dm7" "+addbot Angelyss" "+addbot Arachna" ];
+            openPorts = true;
+          };
+        };
+
+      client1 = client;
+      client2 = client;
+    };
+
+  testScript =
+    ''
+      start_all()
+
+      server.wait_for_unit("openarena")
+      server.wait_until_succeeds("ss --numeric --udp --listening | grep -q 27960")
+
+      client1.wait_for_x()
+      client2.wait_for_x()
+
+      client1.execute("openarena +set r_fullscreen 0 +set name Foo +connect server >&2 &")
+      client2.execute("openarena +set r_fullscreen 0 +set name Bar +connect server >&2 &")
+
+      server.wait_until_succeeds(
+          "journalctl -u openarena -e | grep -q 'Foo.*entered the game'"
+      )
+      server.wait_until_succeeds(
+          "journalctl -u openarena -e | grep -q 'Bar.*entered the game'"
+      )
+
+      server.sleep(10)  # wait for a while to get a nice screenshot
+
+      client1.screenshot("screen_client1_1")
+      client2.screenshot("screen_client2_1")
+
+      client1.block()
+
+      server.sleep(10)
+
+      client1.screenshot("screen_client1_2")
+      client2.screenshot("screen_client2_2")
+
+      client1.unblock()
+
+      server.sleep(10)
+
+      client1.screenshot("screen_client1_3")
+      client2.screenshot("screen_client2_3")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/openldap.nix b/nixpkgs/nixos/tests/openldap.nix
new file mode 100644
index 000000000000..47d6a91843f1
--- /dev/null
+++ b/nixpkgs/nixos/tests/openldap.nix
@@ -0,0 +1,156 @@
+import ./make-test-python.nix ({ pkgs, ... }: let
+  dbContents = ''
+    dn: dc=example
+    objectClass: domain
+    dc: example
+
+    dn: ou=users,dc=example
+    objectClass: organizationalUnit
+    ou: users
+  '';
+
+  ldifConfig = ''
+    dn: cn=config
+    cn: config
+    objectClass: olcGlobal
+    olcLogLevel: stats
+
+    dn: cn=schema,cn=config
+    cn: schema
+    objectClass: olcSchemaConfig
+
+    include: file://${pkgs.openldap}/etc/schema/core.ldif
+    include: file://${pkgs.openldap}/etc/schema/cosine.ldif
+    include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
+
+    dn: olcDatabase={0}config,cn=config
+    olcDatabase: {0}config
+    objectClass: olcDatabaseConfig
+    olcRootDN: cn=root,cn=config
+    olcRootPW: configpassword
+
+    dn: olcDatabase={1}mdb,cn=config
+    objectClass: olcDatabaseConfig
+    objectClass: olcMdbConfig
+    olcDatabase: {1}mdb
+    olcDbDirectory: /var/db/openldap
+    olcDbIndex: objectClass eq
+    olcSuffix: dc=example
+    olcRootDN: cn=root,dc=example
+    olcRootPW: notapassword
+  '';
+
+  ldapClientConfig = {
+    enable = true;
+    loginPam = false;
+    nsswitch = false;
+    server = "ldap://";
+    base = "dc=example";
+  };
+
+in {
+  name = "openldap";
+
+  nodes.machine = { pkgs, ... }: {
+    environment.etc."openldap/root_password".text = "notapassword";
+
+    users.ldap = ldapClientConfig;
+
+    services.openldap = {
+      enable = true;
+      urlList = [ "ldapi:///" "ldap://" ];
+      settings = {
+        children = {
+          "cn=schema".includes = [
+            "${pkgs.openldap}/etc/schema/core.ldif"
+            "${pkgs.openldap}/etc/schema/cosine.ldif"
+            "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+            "${pkgs.openldap}/etc/schema/nis.ldif"
+          ];
+          "olcDatabase={0}config" = {
+            attrs = {
+              objectClass = [ "olcDatabaseConfig" ];
+              olcDatabase = "{0}config";
+              olcRootDN = "cn=root,cn=config";
+              olcRootPW = "configpassword";
+            };
+          };
+          "olcDatabase={1}mdb" = {
+            # This tests string, base64 and path values, as well as lists of string values
+            attrs = {
+              objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+              olcDatabase = "{1}mdb";
+              olcDbDirectory = "/var/lib/openldap/db";
+              olcSuffix = "dc=example";
+              olcRootDN = {
+                # cn=root,dc=example
+                base64 = "Y249cm9vdCxkYz1leGFtcGxl";
+              };
+              olcRootPW = {
+                path = "/etc/openldap/root_password";
+              };
+            };
+          };
+        };
+      };
+    };
+
+    specialisation = {
+      declarativeContents.configuration = { ... }: {
+        services.openldap.declarativeContents."dc=example" = dbContents;
+      };
+      mutableConfig.configuration = { ... }: {
+        services.openldap = {
+          declarativeContents."dc=example" = dbContents;
+          mutableConfig = true;
+        };
+      };
+      manualConfigDir = {
+        inheritParentConfig = false;
+        configuration = { ... }: {
+          users.ldap = ldapClientConfig;
+          services.openldap = {
+            enable = true;
+            configDir = "/var/db/slapd.d";
+          };
+        };
+      };
+    };
+  };
+  testScript = { nodes, ... }: let
+    specializations = "${nodes.machine.system.build.toplevel}/specialisation";
+    changeRootPw = ''
+      dn: olcDatabase={1}mdb,cn=config
+      changetype: modify
+      replace: olcRootPW
+      olcRootPW: foobar
+    '';
+  in ''
+    # Test startup with empty DB
+    machine.wait_for_unit("openldap.service")
+
+    with subtest("declarative contents"):
+      machine.succeed('${specializations}/declarativeContents/bin/switch-to-configuration test')
+      machine.wait_for_unit("openldap.service")
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword')
+      machine.fail('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
+
+    with subtest("mutable config"):
+      machine.succeed('${specializations}/mutableConfig/bin/switch-to-configuration test')
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword')
+      machine.succeed('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar')
+
+    with subtest("manual config dir"):
+      machine.succeed(
+        'mkdir /var/db/slapd.d /var/db/openldap',
+        'slapadd -F /var/db/slapd.d -n0 -l ${pkgs.writeText "config.ldif" ldifConfig}',
+        'slapadd -F /var/db/slapd.d -n1 -l ${pkgs.writeText "contents.ldif" dbContents}',
+        'chown -R openldap:openldap /var/db/slapd.d /var/db/openldap',
+        '${specializations}/manualConfigDir/bin/switch-to-configuration test',
+      )
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword')
+      machine.succeed('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
+      machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/openresty-lua.nix b/nixpkgs/nixos/tests/openresty-lua.nix
new file mode 100644
index 000000000000..9e987398f51d
--- /dev/null
+++ b/nixpkgs/nixos/tests/openresty-lua.nix
@@ -0,0 +1,101 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    lualibs = [
+      pkgs.lua.pkgs.markdown
+    ];
+
+    getPath = lib: type: "${lib}/share/lua/${pkgs.lua.luaversion}/?.${type}";
+    getLuaPath = lib: getPath lib "lua";
+    luaPath = lib.concatStringsSep ";" (map getLuaPath lualibs);
+  in
+  {
+    name = "openresty-lua";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bbigras ];
+    };
+
+    nodes = {
+      webserver = { pkgs, lib, ... }: {
+        networking = {
+          extraHosts = ''
+            127.0.0.1 default.test
+            127.0.0.1 sandbox.test
+          '';
+        };
+        services.nginx = {
+          enable = true;
+          package = pkgs.openresty;
+
+          commonHttpConfig = ''
+            lua_package_path '${luaPath};;';
+          '';
+
+          virtualHosts."default.test" = {
+            default = true;
+            locations."/" = {
+              extraConfig = ''
+                default_type text/html;
+                access_by_lua '
+                  local markdown = require "markdown"
+                  markdown("source")
+                ';
+              '';
+            };
+          };
+
+          virtualHosts."sandbox.test" = {
+            locations."/test1-write" = {
+              extraConfig = ''
+                content_by_lua_block {
+                  local create = os.execute('${pkgs.coreutils}/bin/mkdir /tmp/test1-read')
+                  local create = os.execute('${pkgs.coreutils}/bin/touch /tmp/test1-read/foo.txt')
+                  local echo = os.execute('${pkgs.coreutils}/bin/echo worked > /tmp/test1-read/foo.txt')
+                }
+              '';
+            };
+            locations."/test1-read" = {
+              root = "/tmp";
+            };
+            locations."/test2-write" = {
+              extraConfig = ''
+                content_by_lua_block {
+                  local create = os.execute('${pkgs.coreutils}/bin/mkdir /var/web/test2-read')
+                  local create = os.execute('${pkgs.coreutils}/bin/touch /var/web/test2-read/bar.txt')
+                  local echo = os.execute('${pkgs.coreutils}/bin/echo error-worked > /var/web/test2-read/bar.txt')
+                }
+              '';
+            };
+            locations."/test2-read" = {
+              root = "/var/web";
+            };
+          };
+        };
+      };
+    };
+
+    testScript = { nodes, ... }:
+      ''
+        url = "http://localhost"
+
+        webserver.wait_for_unit("nginx")
+        webserver.wait_for_open_port(80)
+
+        http_code = webserver.succeed(
+          f"curl -w '%{{http_code}}' --head --fail {url}"
+        )
+        assert http_code.split("\n")[-1] == "200"
+
+        # This test checks the creation and reading of a file in sandbox mode.
+        # Checking write in temporary folder
+        webserver.succeed("$(curl -vvv http://sandbox.test/test1-write)")
+        webserver.succeed('test "$(curl -fvvv http://sandbox.test/test1-read/foo.txt)" = worked')
+        # Checking write in protected folder. In sandbox mode for the nginx service, the folder /var/web is mounted
+        # in read-only mode.
+        webserver.succeed("mkdir -p /var/web")
+        webserver.succeed("chown nginx:nginx /var/web")
+        webserver.succeed("$(curl -vvv http://sandbox.test/test2-write)")
+        assert "404 Not Found" in machine.succeed(
+            "curl -vvv -s http://sandbox.test/test2-read/bar.txt"
+        )
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/opensearch.nix b/nixpkgs/nixos/tests/opensearch.nix
new file mode 100644
index 000000000000..2887ac967765
--- /dev/null
+++ b/nixpkgs/nixos/tests/opensearch.nix
@@ -0,0 +1,47 @@
+let
+  opensearchTest =
+    import ./make-test-python.nix (
+      { pkgs, lib, extraSettings ? {} }: {
+        name = "opensearch";
+        meta.maintainers = with pkgs.lib.maintainers; [ shyim ];
+
+        nodes.machine = lib.mkMerge [
+          {
+            virtualisation.memorySize = 2048;
+            services.opensearch.enable = true;
+          }
+          extraSettings
+        ];
+
+        testScript = ''
+          machine.start()
+          machine.wait_for_unit("opensearch.service")
+          machine.wait_for_open_port(9200)
+
+          machine.succeed(
+              "curl --fail localhost:9200"
+          )
+        '';
+      });
+in
+{
+  opensearch = opensearchTest {};
+  opensearchCustomPathAndUser = opensearchTest {
+    extraSettings = {
+      services.opensearch.dataDir = "/var/opensearch_test";
+      services.opensearch.user = "open_search";
+      services.opensearch.group = "open_search";
+      systemd.tmpfiles.rules = [
+        "d /var/opensearch_test 0700 open_search open_search -"
+      ];
+      users = {
+        groups.open_search = {};
+        users.open_search = {
+          description = "OpenSearch daemon user";
+          group = "open_search";
+          isSystemUser = true;
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/tests/opensmtpd-rspamd.nix b/nixpkgs/nixos/tests/opensmtpd-rspamd.nix
new file mode 100644
index 000000000000..e413a2050bd6
--- /dev/null
+++ b/nixpkgs/nixos/tests/opensmtpd-rspamd.nix
@@ -0,0 +1,142 @@
+import ./make-test-python.nix {
+  name = "opensmtpd-rspamd";
+
+  nodes = {
+    smtp1 = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      networking = {
+        firewall.allowedTCPPorts = [ 25 143 ];
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = [ pkgs.opensmtpd ];
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          listen on 0.0.0.0
+          action dovecot_deliver mda \
+            "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
+
+          action do_relay relay
+          # DO NOT DO THIS IN PRODUCTION!
+          # Setting up authentication requires a certificate which is painful in
+          # a test environment, but THIS WOULD BE DANGEROUS OUTSIDE OF A
+          # WELL-CONTROLLED ENVIRONMENT!
+          match from any for any action do_relay
+        '';
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        mailLocation = "maildir:~/mail";
+        protocols = [ "imap" ];
+      };
+    };
+
+    smtp2 = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      networking = {
+        firewall.allowedTCPPorts = [ 25 143 ];
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = [ pkgs.opensmtpd ];
+      services.rspamd = {
+        enable = true;
+        locals."worker-normal.inc".text = ''
+          bind_socket = "127.0.0.1:11333";
+        '';
+      };
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          filter rspamd proc-exec "${pkgs.opensmtpd-filter-rspamd}/bin/filter-rspamd"
+          listen on 0.0.0.0 filter rspamd
+          action dovecot_deliver mda \
+            "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
+        '';
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        mailLocation = "maildir:~/mail";
+        protocols = [ "imap" ];
+      };
+    };
+
+    client = { pkgs, ... }: {
+      networking = {
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.3"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = let
+        sendTestMail = pkgs.writeScriptBin "send-a-test-mail" ''
+          #!${pkgs.python3.interpreter}
+          import smtplib, sys
+
+          with smtplib.SMTP('192.168.1.1') as smtp:
+            smtp.sendmail('alice@[192.168.1.1]', 'bob@[192.168.1.2]', """
+              From: alice@smtp1
+              To: bob@smtp2
+              Subject: Test
+
+              Hello World
+              Here goes the spam test
+              XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
+            """)
+        '';
+
+        checkMailBounced = pkgs.writeScriptBin "check-mail-bounced" ''
+          #!${pkgs.python3.interpreter}
+          import imaplib
+
+          with imaplib.IMAP4('192.168.1.1', 143) as imap:
+            imap.login('alice', 'foobar')
+            imap.select()
+            status, refs = imap.search(None, 'ALL')
+            assert status == 'OK'
+            assert len(refs) == 1
+            status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+            assert status == 'OK'
+            content = msg[0][1]
+            print("===> content:", content)
+            assert b"An error has occurred while attempting to deliver a message" in content
+        '';
+      in [ sendTestMail checkMailBounced ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    client.systemctl("start network-online.target")
+    client.wait_for_unit("network-online.target")
+    smtp1.wait_for_unit("opensmtpd")
+    smtp2.wait_for_unit("opensmtpd")
+    smtp2.wait_for_unit("rspamd")
+    smtp2.wait_for_unit("dovecot2")
+
+    # To prevent sporadic failures during daemon startup, make sure
+    # services are listening on their ports before sending requests
+    smtp1.wait_for_open_port(25)
+    smtp2.wait_for_open_port(25)
+    smtp2.wait_for_open_port(143)
+    smtp2.wait_for_open_port(11333)
+
+    client.succeed("send-a-test-mail")
+    smtp1.wait_until_fails("smtpctl show queue | egrep .")
+    client.succeed("check-mail-bounced >&2")
+  '';
+
+  meta.timeout = 1800;
+}
diff --git a/nixpkgs/nixos/tests/opensmtpd.nix b/nixpkgs/nixos/tests/opensmtpd.nix
new file mode 100644
index 000000000000..d32f82ed33b8
--- /dev/null
+++ b/nixpkgs/nixos/tests/opensmtpd.nix
@@ -0,0 +1,126 @@
+import ./make-test-python.nix {
+  name = "opensmtpd";
+
+  nodes = {
+    smtp1 = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      networking = {
+        firewall.allowedTCPPorts = [ 25 ];
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = [ pkgs.opensmtpd ];
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          listen on 0.0.0.0
+          action do_relay relay
+          # DO NOT DO THIS IN PRODUCTION!
+          # Setting up authentication requires a certificate which is painful in
+          # a test environment, but THIS WOULD BE DANGEROUS OUTSIDE OF A
+          # WELL-CONTROLLED ENVIRONMENT!
+          match from any for any action do_relay
+        '';
+      };
+    };
+
+    smtp2 = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      networking = {
+        firewall.allowedTCPPorts = [ 25 143 ];
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = [ pkgs.opensmtpd ];
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          listen on 0.0.0.0
+          action dovecot_deliver mda \
+            "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
+        '';
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        mailLocation = "maildir:~/mail";
+        protocols = [ "imap" ];
+      };
+    };
+
+    client = { pkgs, ... }: {
+      networking = {
+        useDHCP = false;
+        interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.3"; prefixLength = 24; }
+        ];
+      };
+      environment.systemPackages = let
+        sendTestMail = pkgs.writeScriptBin "send-a-test-mail" ''
+          #!${pkgs.python3.interpreter}
+          import smtplib, sys
+
+          with smtplib.SMTP('192.168.1.1') as smtp:
+            smtp.sendmail('alice@[192.168.1.1]', 'bob@[192.168.1.2]', """
+              From: alice@smtp1
+              To: bob@smtp2
+              Subject: Test
+
+              Hello World
+            """)
+        '';
+
+        checkMailLanded = pkgs.writeScriptBin "check-mail-landed" ''
+          #!${pkgs.python3.interpreter}
+          import imaplib
+
+          with imaplib.IMAP4('192.168.1.2', 143) as imap:
+            imap.login('bob', 'foobar')
+            imap.select()
+            status, refs = imap.search(None, 'ALL')
+            assert status == 'OK'
+            assert len(refs) == 1
+            status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+            assert status == 'OK'
+            content = msg[0][1]
+            print("===> content:", content)
+            split = content.split(b'\r\n')
+            print("===> split:", split)
+            lastline = split[-3]
+            print("===> lastline:", lastline)
+            assert lastline.strip() == b'Hello World'
+        '';
+      in [ sendTestMail checkMailLanded ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    client.systemctl("start network-online.target")
+    client.wait_for_unit("network-online.target")
+    smtp1.wait_for_unit("opensmtpd")
+    smtp2.wait_for_unit("opensmtpd")
+    smtp2.wait_for_unit("dovecot2")
+
+    # To prevent sporadic failures during daemon startup, make sure
+    # services are listening on their ports before sending requests
+    smtp1.wait_for_open_port(25)
+    smtp2.wait_for_open_port(25)
+    smtp2.wait_for_open_port(143)
+
+    client.succeed("send-a-test-mail")
+    smtp1.wait_until_fails("smtpctl show queue | egrep .")
+    smtp2.wait_until_fails("smtpctl show queue | egrep .")
+    client.succeed("check-mail-landed >&2")
+  '';
+
+  meta.timeout = 1800;
+}
diff --git a/nixpkgs/nixos/tests/opensnitch.nix b/nixpkgs/nixos/tests/opensnitch.nix
new file mode 100644
index 000000000000..a1af07647f71
--- /dev/null
+++ b/nixpkgs/nixos/tests/opensnitch.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "opensnitch";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ onny ];
+  };
+
+  nodes = {
+    server =
+      { ... }: {
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        services.caddy = {
+          enable = true;
+          virtualHosts."localhost".extraConfig = ''
+            respond "Hello, world!"
+          '';
+        };
+      };
+
+    clientBlocked =
+      { ... }: {
+        services.opensnitch = {
+          enable = true;
+          settings.DefaultAction = "deny";
+        };
+      };
+
+    clientAllowed =
+      { ... }: {
+        services.opensnitch = {
+          enable = true;
+          settings.DefaultAction = "deny";
+          rules = {
+            curl = {
+              name = "curl";
+              enabled = true;
+              action = "allow";
+              duration = "always";
+              operator = {
+                type ="simple";
+                sensitive = false;
+                operand = "process.path";
+                data = "${pkgs.curl}/bin/curl";
+              };
+            };
+          };
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("caddy.service")
+    server.wait_for_open_port(80)
+
+    clientBlocked.wait_for_unit("opensnitchd.service")
+    clientBlocked.fail("curl http://server")
+
+    clientAllowed.wait_for_unit("opensnitchd.service")
+    clientAllowed.succeed("curl http://server")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/openssh.nix b/nixpkgs/nixos/tests/openssh.nix
new file mode 100644
index 000000000000..8074fd2ed483
--- /dev/null
+++ b/nixpkgs/nixos/tests/openssh.nix
@@ -0,0 +1,213 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let inherit (import ./ssh-keys.nix pkgs)
+      snakeOilPrivateKey snakeOilPublicKey;
+in {
+  name = "openssh";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ aszlig eelco ];
+  };
+
+  nodes = {
+
+    server =
+      { ... }:
+
+      {
+        services.openssh.enable = true;
+        security.pam.services.sshd.limits =
+          [ { domain = "*"; item = "memlock"; type = "-"; value = 1024; } ];
+        users.users.root.openssh.authorizedKeys.keys = [
+          snakeOilPublicKey
+        ];
+      };
+
+    server-lazy =
+      { ... }:
+
+      {
+        services.openssh = { enable = true; startWhenNeeded = true; };
+        security.pam.services.sshd.limits =
+          [ { domain = "*"; item = "memlock"; type = "-"; value = 1024; } ];
+        users.users.root.openssh.authorizedKeys.keys = [
+          snakeOilPublicKey
+        ];
+      };
+
+    server-lazy-socket = {
+      virtualisation.vlans = [ 1 2 ];
+      services.openssh = {
+        enable = true;
+        startWhenNeeded = true;
+        ports = [ 2222 ];
+        listenAddresses = [ { addr = "0.0.0.0"; } ];
+      };
+      users.users.root.openssh.authorizedKeys.keys = [
+        snakeOilPublicKey
+      ];
+    };
+
+    server-localhost-only =
+      { ... }:
+
+      {
+        services.openssh = {
+          enable = true; listenAddresses = [ { addr = "127.0.0.1"; port = 22; } ];
+        };
+      };
+
+    server-localhost-only-lazy =
+      { ... }:
+
+      {
+        services.openssh = {
+          enable = true; startWhenNeeded = true; listenAddresses = [ { addr = "127.0.0.1"; port = 22; } ];
+        };
+      };
+
+    server-match-rule =
+      { ... }:
+
+      {
+        services.openssh = {
+          enable = true; listenAddresses = [ { addr = "127.0.0.1"; port = 22; } { addr = "[::]"; port = 22; } ];
+          extraConfig = ''
+            # Combined test for two (predictable) Match criterias
+            Match LocalAddress 127.0.0.1 LocalPort 22
+              PermitRootLogin yes
+
+            # Separate tests for Match criterias
+            Match User root
+              PermitRootLogin yes
+            Match Group root
+              PermitRootLogin yes
+            Match Host nohost.example
+              PermitRootLogin yes
+            Match LocalAddress 127.0.0.1
+              PermitRootLogin yes
+            Match LocalPort 22
+              PermitRootLogin yes
+            Match RDomain nohost.example
+              PermitRootLogin yes
+            Match Address 127.0.0.1
+              PermitRootLogin yes
+          '';
+        };
+      };
+
+    server_allowedusers =
+      { ... }:
+
+      {
+        services.openssh = { enable = true; settings.AllowUsers = [ "alice" "bob" ]; };
+        users.groups = { alice = { }; bob = { }; carol = { }; };
+        users.users = {
+          alice = { isNormalUser = true; group = "alice"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+          bob = { isNormalUser = true; group = "bob"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+          carol = { isNormalUser = true; group = "carol"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+        };
+      };
+
+    client =
+      { ... }: {
+        virtualisation.vlans = [ 1 2 ];
+      };
+
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("sshd", timeout=30)
+    server_localhost_only.wait_for_unit("sshd", timeout=30)
+    server_match_rule.wait_for_unit("sshd", timeout=30)
+
+    server_lazy.wait_for_unit("sshd.socket", timeout=30)
+    server_localhost_only_lazy.wait_for_unit("sshd.socket", timeout=30)
+    server_lazy_socket.wait_for_unit("sshd.socket", timeout=30)
+
+    with subtest("manual-authkey"):
+        client.succeed("mkdir -m 700 /root/.ssh")
+        client.succeed(
+            '${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""'
+        )
+        public_key = client.succeed(
+            "${pkgs.openssh}/bin/ssh-keygen -y -f /root/.ssh/id_ed25519"
+        )
+        public_key = public_key.strip()
+        client.succeed("chmod 600 /root/.ssh/id_ed25519")
+
+        server.succeed("mkdir -m 700 /root/.ssh")
+        server.succeed("echo '{}' > /root/.ssh/authorized_keys".format(public_key))
+        server_lazy.succeed("mkdir -m 700 /root/.ssh")
+        server_lazy.succeed("echo '{}' > /root/.ssh/authorized_keys".format(public_key))
+
+        client.wait_for_unit("network.target")
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2",
+            timeout=30
+        )
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024",
+            timeout=30
+        )
+
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server-lazy 'echo hello world' >&2",
+            timeout=30
+        )
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server-lazy 'ulimit -l' | grep 1024",
+            timeout=30
+        )
+
+    with subtest("socket activation on a non-standard port"):
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ssh -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.2.4 true",
+            timeout=30
+        )
+
+    with subtest("configured-authkey"):
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server true",
+            timeout=30
+        )
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-lazy true",
+            timeout=30
+        )
+
+    with subtest("localhost-only"):
+        server_localhost_only.succeed("ss -nlt | grep '127.0.0.1:22'")
+        server_localhost_only_lazy.succeed("ss -nlt | grep '127.0.0.1:22'")
+
+    with subtest("match-rules"):
+        server_match_rule.succeed("ss -nlt | grep '127.0.0.1:22'")
+
+    with subtest("allowed-users"):
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil alice@server_allowedusers true",
+            timeout=30
+        )
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil bob@server_allowedusers true",
+            timeout=30
+        )
+        client.fail(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil carol@server_allowedusers true",
+            timeout=30
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/openstack-image.nix b/nixpkgs/nixos/tests/openstack-image.nix
new file mode 100644
index 000000000000..0b57dfb8e7eb
--- /dev/null
+++ b/nixpkgs/nixos/tests/openstack-image.nix
@@ -0,0 +1,98 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+with import common/ec2.nix { inherit makeTest pkgs; };
+
+let
+  image = (import ../lib/eval-config.nix {
+    inherit system;
+    modules = [
+      ../maintainers/scripts/openstack/openstack-image.nix
+      ../modules/testing/test-instrumentation.nix
+      ../modules/profiles/qemu-guest.nix
+      {
+        # Needed by nixos-rebuild due to lack of network access.
+        system.extraDependencies = with pkgs; [
+          stdenv
+        ];
+      }
+    ];
+  }).config.system.build.openstackImage + "/nixos.qcow2";
+
+  sshKeys = import ./ssh-keys.nix pkgs;
+  snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text;
+  snakeOilPrivateKeyFile = pkgs.writeText "private-key" snakeOilPrivateKey;
+  snakeOilPublicKey = sshKeys.snakeOilPublicKey;
+
+in {
+  metadata = makeEc2Test {
+    name = "openstack-ec2-metadata";
+    inherit image;
+    sshPublicKey = snakeOilPublicKey;
+    userData = ''
+      SSH_HOST_ED25519_KEY_PUB:${snakeOilPublicKey}
+      SSH_HOST_ED25519_KEY:${replaceStrings ["\n"] ["|"] snakeOilPrivateKey}
+    '';
+    script = ''
+      machine.start()
+      machine.wait_for_file("/etc/ec2-metadata/user-data")
+      machine.wait_for_unit("sshd.service")
+
+      machine.succeed("grep unknown /etc/ec2-metadata/ami-manifest-path")
+
+      # We have no keys configured on the client side yet, so this should fail
+      machine.fail("ssh -o BatchMode=yes localhost exit")
+
+      # Let's install our client private key
+      machine.succeed("mkdir -p ~/.ssh")
+
+      machine.copy_from_host_via_shell(
+          "${snakeOilPrivateKeyFile}", "~/.ssh/id_ed25519"
+      )
+      machine.succeed("chmod 600 ~/.ssh/id_ed25519")
+
+      # We haven't configured the host key yet, so this should still fail
+      machine.fail("ssh -o BatchMode=yes localhost exit")
+
+      # Add the host key; ssh should finally succeed
+      machine.succeed(
+          "echo localhost,127.0.0.1 ${snakeOilPublicKey} > ~/.ssh/known_hosts"
+      )
+      machine.succeed("ssh -o BatchMode=yes localhost exit")
+
+      # Just to make sure resizing is idempotent.
+      machine.shutdown()
+      machine.start()
+      machine.wait_for_file("/etc/ec2-metadata/user-data")
+    '';
+  };
+
+  userdata = makeEc2Test {
+    name = "openstack-ec2-metadata";
+    inherit image;
+    sshPublicKey = snakeOilPublicKey;
+    userData = ''
+      { pkgs, ... }:
+      {
+        imports = [
+          <nixpkgs/nixos/modules/virtualisation/openstack-config.nix>
+          <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          <nixpkgs/nixos/modules/profiles/qemu-guest.nix>
+        ];
+        environment.etc.testFile = {
+          text = "whoa";
+        };
+      }
+    '';
+    script = ''
+      machine.start()
+      machine.wait_for_file("/etc/testFile")
+      assert "whoa" in machine.succeed("cat /etc/testFile")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/opentabletdriver.nix b/nixpkgs/nixos/tests/opentabletdriver.nix
new file mode 100644
index 000000000000..a71a007c4110
--- /dev/null
+++ b/nixpkgs/nixos/tests/opentabletdriver.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ( { pkgs, ... }: let
+  testUser = "alice";
+in {
+  name = "opentabletdriver";
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ thiagokokada ];
+  };
+
+  nodes.machine = { pkgs, ... }:
+    {
+      imports = [
+        ./common/user-account.nix
+        ./common/x11.nix
+      ];
+      test-support.displayManager.auto.user = testUser;
+      hardware.opentabletdriver.enable = true;
+    };
+
+  testScript =
+    ''
+      machine.start()
+      machine.wait_for_x()
+
+      machine.wait_for_unit('graphical.target')
+      machine.wait_for_unit("opentabletdriver.service", "${testUser}")
+
+      machine.succeed("cat /etc/udev/rules.d/70-opentabletdriver.rules")
+      # Will fail if service is not running
+      # Needs to run as the same user that started the service
+      machine.succeed("su - ${testUser} -c 'otd detect'")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/opentelemetry-collector.nix b/nixpkgs/nixos/tests/opentelemetry-collector.nix
new file mode 100644
index 000000000000..9a56a22ca47e
--- /dev/null
+++ b/nixpkgs/nixos/tests/opentelemetry-collector.nix
@@ -0,0 +1,76 @@
+import ./make-test-python.nix ({ pkgs, ...} : let
+  port = 4318;
+in {
+  name = "opentelemetry-collector";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tylerjl ];
+  };
+
+  nodes.machine = { ... }: {
+    networking.firewall.allowedTCPPorts = [ port ];
+    services.opentelemetry-collector = {
+      enable = true;
+      settings = {
+        exporters.logging.verbosity = "detailed";
+        receivers.otlp.protocols.http = {};
+        service = {
+          pipelines.logs = {
+            receivers = [ "otlp" ];
+            exporters = [ "logging" ];
+          };
+        };
+      };
+    };
+    virtualisation.forwardPorts = [{
+      host.port = port;
+      guest.port = port;
+    }];
+  };
+
+  extraPythonPackages = p: [
+    p.requests
+    p.types-requests
+  ];
+
+  # Send a log event through the OTLP pipeline and check for its
+  # presence in the collector logs.
+  testScript = /* python */ ''
+    import requests
+    import time
+
+    from uuid import uuid4
+
+    flag = str(uuid4())
+
+    machine.wait_for_unit("opentelemetry-collector.service")
+    machine.wait_for_open_port(${toString port})
+
+    event = {
+        "resourceLogs": [
+            {
+                "resource": {"attributes": []},
+                "scopeLogs": [
+                    {
+                        "logRecords": [
+                            {
+                                "timeUnixNano": str(time.time_ns()),
+                                "severityNumber": 9,
+                                "severityText": "Info",
+                                "name": "logTest",
+                                "body": {
+                                    "stringValue": flag
+                                },
+                                "attributes": []
+                            },
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+
+    response = requests.post("http://localhost:${toString port}/v1/logs", json=event)
+    assert response.status_code == 200
+    assert flag in machine.execute("journalctl -u opentelemetry-collector")[-1]
+  '';
+})
diff --git a/nixpkgs/nixos/tests/openvscode-server.nix b/nixpkgs/nixos/tests/openvscode-server.nix
new file mode 100644
index 000000000000..cbff8e09c593
--- /dev/null
+++ b/nixpkgs/nixos/tests/openvscode-server.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+{
+  name = "openvscode-server";
+
+  nodes = {
+    machine = {pkgs, ...}: {
+      services.openvscode-server = {
+        enable = true;
+        withoutConnectionToken = true;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("openvscode-server.service")
+    machine.wait_for_open_port(3000)
+    machine.succeed("curl -k --fail http://localhost:3000", timeout=10)
+  '';
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+})
diff --git a/nixpkgs/nixos/tests/orangefs.nix b/nixpkgs/nixos/tests/orangefs.nix
new file mode 100644
index 000000000000..4e67a7fb8efe
--- /dev/null
+++ b/nixpkgs/nixos/tests/orangefs.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ ... } :
+
+let
+  server = { pkgs, ... } : {
+    networking.firewall.allowedTCPPorts = [ 3334 ];
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb
+    '';
+
+    virtualisation.emptyDiskImages = [ 4096 ];
+
+    virtualisation.fileSystems =
+      { "/data" =
+          { device = "/dev/disk/by-label/data";
+            fsType = "ext4";
+          };
+      };
+
+    services.orangefs.server = {
+      enable = true;
+      dataStorageSpace = "/data/storage";
+      metadataStorageSpace = "/data/meta";
+      servers = {
+        server1 = "tcp://server1:3334";
+        server2 = "tcp://server2:3334";
+      };
+    };
+  };
+
+  client = { lib, ... } : {
+    networking.firewall.enable = true;
+
+    services.orangefs.client = {
+      enable = true;
+      fileSystems = [{
+        target = "tcp://server1:3334/orangefs";
+        mountPoint = "/orangefs";
+      }];
+    };
+  };
+
+in {
+  name = "orangefs";
+
+  nodes = {
+    server1 = server;
+    server2 = server;
+
+    client1 = client;
+    client2 = client;
+  };
+
+  testScript = ''
+    # format storage
+    for server in server1, server2:
+        server.start()
+        server.wait_for_unit("multi-user.target")
+        server.succeed("mkdir -p /data/storage /data/meta")
+        server.succeed("chown orangefs:orangefs /data/storage /data/meta")
+        server.succeed("chmod 0770 /data/storage /data/meta")
+        server.succeed(
+            "sudo -g orangefs -u orangefs pvfs2-server -f /etc/orangefs/server.conf"
+        )
+
+    # start services after storage is formatted on all machines
+    for server in server1, server2:
+        server.succeed("systemctl start orangefs-server.service")
+
+    with subtest("clients can reach and mount the FS"):
+        for client in client1, client2:
+            client.start()
+            client.wait_for_unit("orangefs-client.service")
+            # Both servers need to be reachable
+            client.succeed("pvfs2-check-server -h server1 -f orangefs -n tcp -p 3334")
+            client.succeed("pvfs2-check-server -h server2 -f orangefs -n tcp -p 3334")
+            client.wait_for_unit("orangefs.mount")
+
+    with subtest("R/W test between clients"):
+        client1.succeed("echo test > /orangefs/file1")
+        client2.succeed("grep test /orangefs/file1")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/os-prober.nix b/nixpkgs/nixos/tests/os-prober.nix
new file mode 100644
index 000000000000..034de0620d88
--- /dev/null
+++ b/nixpkgs/nixos/tests/os-prober.nix
@@ -0,0 +1,133 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+let
+  # A filesystem image with a (presumably) bootable debian
+  debianImage = pkgs.vmTools.diskImageFuns.debian11i386 {
+    # os-prober cannot detect systems installed on disks without a partition table
+    # so we create the disk ourselves
+    createRootFS = with pkgs; ''
+      ${parted}/bin/parted --script /dev/vda mklabel msdos
+      ${parted}/sbin/parted --script /dev/vda -- mkpart primary ext2 1M -1s
+      mkdir /mnt
+      ${e2fsprogs}/bin/mkfs.ext4 -O '^metadata_csum_seed' /dev/vda1
+      ${util-linux}/bin/mount -t ext4 /dev/vda1 /mnt
+
+      if test -e /mnt/.debug; then
+        exec ${bash}/bin/sh
+      fi
+      touch /mnt/.debug
+
+      mkdir /mnt/proc /mnt/dev /mnt/sys
+    '';
+    extraPackages = [
+      # /etc/os-release
+      "base-files"
+      # make the disk bootable-looking
+      "grub2" "linux-image-686"
+    ];
+    # install grub
+    postInstall = ''
+      ln -sf /proc/self/mounts > /etc/mtab
+      PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
+        grub-install /dev/vda --force
+      PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
+        update-grub
+    '';
+  };
+
+  # a part of the configuration of the test vm
+  simpleConfig = {
+    boot.loader.grub = {
+      enable = true;
+      useOSProber = true;
+      device = "/dev/vda";
+      # vda is a filesystem without partition table
+      forceInstall = true;
+    };
+    nix.settings = {
+      substituters = lib.mkForce [];
+      hashed-mirrors = null;
+      connect-timeout = 1;
+    };
+    # save some memory
+    documentation.enable = false;
+  };
+  # /etc/nixos/configuration.nix for the vm
+  configFile = pkgs.writeText "configuration.nix"  ''
+    {config, pkgs, lib, ...}: ({
+    imports =
+          [ ./hardware-configuration.nix
+            <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          ];
+    } // lib.importJSON ${
+      pkgs.writeText "simpleConfig.json" (builtins.toJSON simpleConfig)
+    })
+  '';
+in {
+  name = "os-prober";
+
+  nodes.machine = { config, pkgs, ... }: (simpleConfig // {
+      imports = [ ../modules/profiles/installation-device.nix
+                  ../modules/profiles/base.nix ];
+      virtualisation.memorySize = 1300;
+      # To add the secondary disk:
+      virtualisation.qemu.options = [ "-drive index=2,file=${debianImage}/disk-image.qcow2,read-only,if=virtio" ];
+
+      # The test cannot access the network, so any packages
+      # nixos-rebuild needs must be included in the VM.
+      system.extraDependencies = with pkgs;
+        [
+          bintools
+          brotli
+          brotli.dev
+          brotli.lib
+          desktop-file-utils
+          docbook5
+          docbook_xsl_ns
+          grub2
+          kbd
+          kbd.dev
+          kmod.dev
+          libarchive
+          libarchive.dev
+          libxml2.bin
+          libxslt.bin
+          nixos-artwork.wallpapers.simple-dark-gray-bottom
+          ntp
+          perlPackages.ListCompare
+          perlPackages.XMLLibXML
+          python3
+          shared-mime-info
+          stdenv
+          sudo
+          texinfo
+          unionfs-fuse
+          xorg.lndir
+
+          # add curl so that rather than seeing the test attempt to download
+          # curl's tarball, we see what it's trying to download
+          curl
+        ];
+  });
+
+  testScript = ''
+    machine.start()
+    machine.succeed("udevadm settle")
+    machine.wait_for_unit("multi-user.target")
+    print(machine.succeed("lsblk"))
+
+    # check that os-prober works standalone
+    machine.succeed(
+        "${pkgs.os-prober}/bin/os-prober | grep /dev/vdb1"
+    )
+
+    # rebuild and test that debian is available in the grub menu
+    machine.succeed("nixos-generate-config")
+    machine.copy_from_host(
+        "${configFile}",
+        "/etc/nixos/configuration.nix",
+    )
+    machine.succeed("nixos-rebuild boot --show-trace >&2")
+
+    machine.succeed("egrep 'menuentry.*debian' /boot/grub/grub.cfg")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/osquery.nix b/nixpkgs/nixos/tests/osquery.nix
new file mode 100644
index 000000000000..e98e7c1baf04
--- /dev/null
+++ b/nixpkgs/nixos/tests/osquery.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  config_refresh = "10";
+  nullvalue = "NULL";
+  utc = false;
+in
+{
+  name = "osquery";
+  meta.maintainers = with lib.maintainers; [ znewman01 lewo ];
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.osquery = {
+      enable = true;
+
+      settings.options = { inherit nullvalue utc; };
+      flags = {
+        inherit config_refresh;
+        nullvalue = "IGNORED";
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      cfg = nodes.machine.services.osquery;
+    in
+    ''
+      machine.start()
+      machine.wait_for_unit("osqueryd.service")
+
+      # Stop the osqueryd service so that we can use osqueryi to check information stored in the database.
+      machine.wait_until_succeeds("systemctl stop osqueryd.service")
+
+      # osqueryd was able to query information about the host.
+      machine.succeed("echo 'SELECT address FROM etc_hosts LIMIT 1;' | osqueryi | tee /dev/console | grep -q '127.0.0.1'")
+
+      # osquery binaries respect configuration from the Nix config option.
+      machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"utc\";' | osqueryi | tee /dev/console | grep -q ${lib.boolToString utc}")
+
+      # osquery binaries respect configuration from the Nix flags option.
+      machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"config_refresh\";' | osqueryi | tee /dev/console | grep -q ${config_refresh}")
+
+      # Demonstrate that osquery binaries prefer configuration plugin options over CLI flags.
+      # https://osquery.readthedocs.io/en/latest/deployment/configuration/#options.
+      machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"nullvalue\";' | osqueryi | tee /dev/console | grep -q ${nullvalue}")
+
+      # Module creates directories for default database_path and pidfile flag values.
+      machine.succeed("test -d $(dirname ${cfg.flags.database_path})")
+      machine.succeed("test -d $(dirname ${cfg.flags.pidfile})")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/osrm-backend.nix b/nixpkgs/nixos/tests/osrm-backend.nix
new file mode 100644
index 000000000000..b0e65a2ae1c1
--- /dev/null
+++ b/nixpkgs/nixos/tests/osrm-backend.nix
@@ -0,0 +1,57 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  port = 5000;
+in {
+  name = "osrm-backend";
+  meta.maintainers = [ lib.maintainers.erictapen ];
+
+  nodes.machine = { config, pkgs, ... }:{
+
+    services.osrm = {
+      enable = true;
+      inherit port;
+      dataFile = let
+        filename = "monaco";
+        osrm-data = pkgs.stdenv.mkDerivation {
+          name = "osrm-data";
+
+          buildInputs = [ pkgs.osrm-backend ];
+
+          # This is a pbf file of monaco, downloaded at 2019-01-04 from
+          # http://download.geofabrik.de/europe/monaco-latest.osm.pbf
+          # as apparently no provider of OSM files guarantees immutability,
+          # this is hosted as a gist on GitHub.
+          src = pkgs.fetchgit {
+            url = "https://gist.github.com/erictapen/01e39f73a6c856eac53ba809a94cdb83";
+            rev = "9b1ff0f24deb40e5cf7df51f843dbe860637b8ce";
+            sha256 = "1scqhmrfnpwsy5i2a9jpggqnvfgj4hv9p4qyvc79321pzkbv59nx";
+          };
+
+          buildCommand = ''
+            cp $src/${filename}.osm.pbf .
+            ${pkgs.osrm-backend}/bin/osrm-extract -p ${pkgs.osrm-backend}/share/osrm/profiles/car.lua ${filename}.osm.pbf
+            ${pkgs.osrm-backend}/bin/osrm-partition ${filename}.osrm
+            ${pkgs.osrm-backend}/bin/osrm-customize ${filename}.osrm
+            mkdir -p $out
+            cp ${filename}* $out/
+          '';
+        };
+      in "${osrm-data}/${filename}.osrm";
+    };
+
+    environment.systemPackages = [ pkgs.jq ];
+  };
+
+  testScript = let
+    query = "http://localhost:${toString port}/route/v1/driving/7.41720,43.73304;7.42463,43.73886?steps=true";
+  in ''
+    machine.wait_for_unit("osrm.service")
+    machine.wait_for_open_port(${toString port})
+    assert "Boulevard Rainier III" in machine.succeed(
+        "curl --fail --silent '${query}' | jq .waypoints[0].name"
+    )
+    assert "Avenue de la Costa" in machine.succeed(
+        "curl --fail --silent '${query}' | jq .waypoints[1].name"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/outline.nix b/nixpkgs/nixos/tests/outline.nix
new file mode 100644
index 000000000000..e45be37f5d3b
--- /dev/null
+++ b/nixpkgs/nixos/tests/outline.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  accessKey = "BKIKJAA5BMMU2RHO6IBB";
+  secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
+  secretKeyFile = pkgs.writeText "outline-secret-key" ''
+    ${secretKey}
+  '';
+  rootCredentialsFile = pkgs.writeText "minio-credentials-full" ''
+    MINIO_ROOT_USER=${accessKey}
+    MINIO_ROOT_PASSWORD=${secretKey}
+  '';
+in
+{
+  name = "outline";
+
+  meta.maintainers = with lib.maintainers; [ xanderio ];
+
+  nodes = {
+    outline = { pkgs, config, ... }: {
+      nixpkgs.config.allowUnfree = true;
+      environment.systemPackages = [ pkgs.minio-client ];
+      services.outline = {
+        enable = true;
+        forceHttps = false;
+        storage = {
+          inherit accessKey secretKeyFile;
+          uploadBucketUrl = "http://localhost:9000";
+          uploadBucketName = "outline";
+          region = config.services.minio.region;
+        };
+      };
+      services.minio = {
+        enable = true;
+        inherit rootCredentialsFile;
+      };
+    };
+  };
+
+  testScript =
+    ''
+      machine.wait_for_unit("minio.service")
+      machine.wait_for_open_port(9000)
+
+      # Create a test bucket on the server
+      machine.succeed(
+          "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4"
+      )
+      machine.succeed("mc mb minio/outline")
+
+      outline.wait_for_unit("outline.service")
+      outline.wait_for_open_port(3000)
+      outline.succeed("curl --fail http://localhost:3000/")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/overlayfs.nix b/nixpkgs/nixos/tests/overlayfs.nix
new file mode 100644
index 000000000000..6dab6760c5b9
--- /dev/null
+++ b/nixpkgs/nixos/tests/overlayfs.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "overlayfs";
+  meta.maintainers = with pkgs.lib.maintainers; [ bachp ];
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.emptyDiskImages = [ 512 ];
+    networking.hostId = "deadbeef";
+    environment.systemPackages = with pkgs; [ parted ];
+  };
+
+  testScript = ''
+    machine.succeed("ls /dev")
+
+    machine.succeed("mkdir -p /tmp/mnt")
+
+    # Test ext4 + overlayfs
+    machine.succeed(
+      'mkfs.ext4 -F -L overlay-ext4 /dev/vdb',
+      'mount -t ext4 /dev/vdb /tmp/mnt',
+      'mkdir -p /tmp/mnt/upper /tmp/mnt/lower /tmp/mnt/work /tmp/mnt/merged',
+      # Setup some existing files
+      'echo Replace > /tmp/mnt/lower/replace.txt',
+      'echo Append > /tmp/mnt/lower/append.txt',
+      'echo Overwrite > /tmp/mnt/lower/overwrite.txt',
+      'mount -t overlay overlay -o lowerdir=/tmp/mnt/lower,upperdir=/tmp/mnt/upper,workdir=/tmp/mnt/work /tmp/mnt/merged',
+      # Test new
+      'echo New > /tmp/mnt/merged/new.txt',
+      '[[ "$(cat /tmp/mnt/merged/new.txt)" == New ]]',
+      # Test replace
+      '[[ "$(cat /tmp/mnt/merged/replace.txt)" == Replace ]]',
+      'echo Replaced > /tmp/mnt/merged/replace-tmp.txt',
+      'mv /tmp/mnt/merged/replace-tmp.txt /tmp/mnt/merged/replace.txt',
+      '[[ "$(cat /tmp/mnt/merged/replace.txt)" == Replaced ]]',
+      # Overwrite
+      '[[ "$(cat /tmp/mnt/merged/overwrite.txt)" == Overwrite ]]',
+      'echo Overwritten > /tmp/mnt/merged/overwrite.txt',
+      '[[ "$(cat /tmp/mnt/merged/overwrite.txt)" == Overwritten ]]',
+      # Test append
+      '[[ "$(cat /tmp/mnt/merged/append.txt)" == Append ]]',
+      'echo ed >> /tmp/mnt/merged/append.txt',
+      '[[ "$(cat /tmp/mnt/merged/append.txt)" == "Append\ned" ]]',
+      'umount /tmp/mnt/merged',
+      'umount /tmp/mnt',
+      'udevadm settle',
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/owncast.nix b/nixpkgs/nixos/tests/owncast.nix
new file mode 100644
index 000000000000..73aac4e70475
--- /dev/null
+++ b/nixpkgs/nixos/tests/owncast.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "owncast";
+  meta = with pkgs.lib.maintainers; { maintainers = [ MayNiklas ]; };
+
+  nodes = {
+    client = { pkgs, ... }: with pkgs.lib; {
+      networking = {
+        dhcpcd.enable = false;
+        interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::2"; prefixLength = 64; } ];
+        interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.2"; prefixLength = 24; } ];
+      };
+    };
+    server = { pkgs, ... }: with pkgs.lib; {
+      networking = {
+        dhcpcd.enable = false;
+        useNetworkd = true;
+        useDHCP = false;
+        interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::1"; prefixLength = 64; } ];
+        interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.1"; prefixLength = 24; } ];
+
+        firewall.allowedTCPPorts = [ 8080 ];
+      };
+
+      services.owncast = {
+        enable = true;
+        listen = "0.0.0.0";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    client.systemctl("start network-online.target")
+    server.systemctl("start network-online.target")
+    client.wait_for_unit("network-online.target")
+    server.wait_for_unit("network-online.target")
+    server.wait_for_unit("owncast.service")
+    server.wait_until_succeeds("ss -ntl | grep -q 8080")
+
+    client.succeed("curl http://192.168.1.1:8080/api/status")
+    client.succeed("curl http://[fd00::1]:8080/api/status")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pacemaker.nix b/nixpkgs/nixos/tests/pacemaker.nix
new file mode 100644
index 000000000000..684557614953
--- /dev/null
+++ b/nixpkgs/nixos/tests/pacemaker.nix
@@ -0,0 +1,110 @@
+import ./make-test-python.nix  ({ pkgs, lib, ... }: rec {
+  name = "pacemaker";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ astro ];
+  };
+
+  nodes =
+    let
+      node = i: {
+        networking.interfaces.eth1.ipv4.addresses = [ {
+          address = "192.168.0.${toString i}";
+          prefixLength = 24;
+        } ];
+
+        services.corosync = {
+          enable = true;
+          clusterName = "zentralwerk-network";
+          nodelist = lib.imap (i: name: {
+            nodeid = i;
+            inherit name;
+            ring_addrs = [
+              (builtins.head nodes.${name}.networking.interfaces.eth1.ipv4.addresses).address
+            ];
+          }) (builtins.attrNames nodes);
+        };
+        environment.etc."corosync/authkey" = {
+          source = builtins.toFile "authkey"
+            # minimum length: 128 bytes
+            "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest";
+          mode = "0400";
+        };
+
+        services.pacemaker.enable = true;
+
+        # used for pacemaker resource
+        systemd.services.ha-cat = {
+          description = "Highly available netcat";
+          serviceConfig.ExecStart = "${pkgs.netcat}/bin/nc -l discard";
+        };
+      };
+    in {
+      node1 = node 1;
+      node2 = node 2;
+      node3 = node 3;
+    };
+
+  # sets up pacemaker with resources configuration, then crashes a
+  # node and waits for service restart on another node
+  testScript =
+    let
+      resources = builtins.toFile "cib-resources.xml" ''
+        <resources>
+          <primitive id="cat" class="systemd" type="ha-cat">
+            <operations>
+              <op id="stop-cat" name="start" interval="0" timeout="1s"/>
+              <op id="start-cat" name="start" interval="0" timeout="1s"/>
+              <op id="monitor-cat" name="monitor" interval="1s" timeout="1s"/>
+            </operations>
+          </primitive>
+        </resources>
+      '';
+    in ''
+      import re
+      import time
+
+      start_all()
+
+      ${lib.concatMapStrings (node: ''
+        ${node}.wait_until_succeeds("corosync-quorumtool")
+        ${node}.wait_for_unit("pacemaker.service")
+      '') (builtins.attrNames nodes)}
+
+      # No STONITH device
+      node1.succeed("crm_attribute -t crm_config -n stonith-enabled -v false")
+      # Configure the cat resource
+      node1.succeed("cibadmin --replace --scope resources --xml-file ${resources}")
+
+      # wait until the service is started
+      while True:
+        output = node1.succeed("crm_resource -r cat --locate")
+        match = re.search("is running on: (.+)", output)
+        if match:
+          for machine in machines:
+            if machine.name == match.group(1):
+              current_node = machine
+          break
+        time.sleep(1)
+
+      current_node.log("Service running here!")
+      current_node.crash()
+
+      # pick another node that's still up
+      for machine in machines:
+        if machine.booted:
+          check_node = machine
+      # find where the service has been started next
+      while True:
+        output = check_node.succeed("crm_resource -r cat --locate")
+        match = re.search("is running on: (.+)", output)
+        # output will remain the old current_node until the crash is detected by pacemaker
+        if match and match.group(1) != current_node.name:
+          for machine in machines:
+            if machine.name == match.group(1):
+              next_node = machine
+          break
+        time.sleep(1)
+
+      next_node.log("Service migrated here!")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/packagekit.nix b/nixpkgs/nixos/tests/packagekit.nix
new file mode 100644
index 000000000000..5769c6c9a8d4
--- /dev/null
+++ b/nixpkgs/nixos/tests/packagekit.nix
@@ -0,0 +1,25 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "packagekit";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ peterhoeg ];
+  };
+
+  nodes.machine = { ... }: {
+    environment.systemPackages = with pkgs; [ dbus ];
+    services.packagekit = {
+      enable = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    # send a dbus message to activate the service
+    machine.succeed(
+        "dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.PackageKit /org/freedesktop/PackageKit org.freedesktop.DBus.Introspectable.Introspect"
+    )
+
+    # so now it should be running
+    machine.wait_for_unit("packagekit.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pam/pam-file-contents.nix b/nixpkgs/nixos/tests/pam/pam-file-contents.nix
new file mode 100644
index 000000000000..accaa4cc70a9
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/pam-file-contents.nix
@@ -0,0 +1,26 @@
+let
+  name = "pam";
+in
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "pam-file-contents";
+
+  nodes.machine = { ... }: {
+    imports = [ ../../modules/profiles/minimal.nix ];
+
+    security.krb5.enable = true;
+
+    users = {
+      mutableUsers = false;
+      users = {
+        user = {
+          isNormalUser = true;
+        };
+      };
+    };
+  };
+
+  testScript = builtins.replaceStrings
+    [ "@@pam_ccreds@@" "@@pam_krb5@@" ]
+    [ pkgs.pam_ccreds.outPath pkgs.pam_krb5.outPath ]
+    (builtins.readFile ./test_chfn.py);
+})
diff --git a/nixpkgs/nixos/tests/pam/pam-oath-login.nix b/nixpkgs/nixos/tests/pam/pam-oath-login.nix
new file mode 100644
index 000000000000..dd6ef4a0abcb
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/pam-oath-login.nix
@@ -0,0 +1,108 @@
+import ../make-test-python.nix ({ ... }:
+
+let
+  oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3";
+
+  # With HOTP mode the password is calculated based on a counter of
+  # how many passwords have been made. In this env, we'll always be on
+  # the 0th counter, so the password is static.
+  #
+  # Generated in nix-shell -p oath-toolkit
+  # via: oathtool -v -d6 -w10 cdd4083ef8ff1fa9178c6d46bfb1a3
+  # and picking a the first 4:
+  oathSnakeOilPassword1 = "143349";
+  oathSnakeOilPassword2 = "801753";
+
+  alicePassword = "foobar";
+  # Generated via: mkpasswd -m sha-512 and passing in "foobar"
+  hashedAlicePassword = "$6$MsMrE1q.1HrCgTS$Vq2e/uILzYjSN836TobAyN9xh9oi7EmCmucnZID25qgPoibkw8qTCugiAPnn4eCGvn1A.7oEBFJaaGUaJsQQY.";
+
+in
+{
+  name = "pam-oath-login";
+
+  nodes.machine =
+    { ... }:
+    {
+      security.pam.oath = {
+        enable = true;
+      };
+
+      users.users.alice = {
+        isNormalUser = true;
+        name = "alice";
+        uid = 1000;
+        hashedPassword = hashedAlicePassword;
+        extraGroups = [ "wheel" ];
+        createHome = true;
+        home = "/home/alice";
+      };
+
+
+      systemd.services.setupOathSnakeoilFile = {
+        wantedBy = [ "default.target" ];
+        before = [ "default.target" ];
+        unitConfig = {
+          type = "oneshot";
+          RemainAfterExit = true;
+        };
+        script = ''
+          touch /etc/users.oath
+          chmod 600 /etc/users.oath
+          chown root /etc/users.oath
+          echo "HOTP/E/6 alice - ${oathSnakeoilSecret}" > /etc/users.oath
+        '';
+      };
+    };
+
+  testScript = ''
+    def switch_to_tty(tty_number):
+        machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'")
+        machine.send_key(f"alt-f{tty_number}")
+        machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]")
+        machine.wait_for_unit(f"getty@tty{tty_number}.service")
+        machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'")
+
+
+    def enter_user_alice(tty_number):
+        machine.wait_until_tty_matches(tty_number, "login: ")
+        machine.send_chars("alice\n")
+        machine.wait_until_tty_matches(tty_number, "login: alice")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches(tty_number, "One-time password")
+
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    machine.screenshot("postboot")
+
+    with subtest("Invalid password"):
+        switch_to_tty("2")
+        enter_user_alice("2")
+
+        machine.send_chars("${oathSnakeOilPassword1}\n")
+        machine.wait_until_tty_matches("2", "Password: ")
+        machine.send_chars("blorg\n")
+        machine.wait_until_tty_matches("2", "Login incorrect")
+
+    with subtest("Invalid oath token"):
+        switch_to_tty("3")
+        enter_user_alice("3")
+
+        machine.send_chars("000000\n")
+        machine.wait_until_tty_matches("3", "Login incorrect")
+        machine.wait_until_tty_matches("3", "login:")
+
+    with subtest("Happy path: Both passwords are mandatory to get us in"):
+        switch_to_tty("4")
+        enter_user_alice("4")
+
+        machine.send_chars("${oathSnakeOilPassword2}\n")
+        machine.wait_until_tty_matches("4", "Password: ")
+        machine.send_chars("${alicePassword}\n")
+
+        machine.wait_until_succeeds("pgrep -u alice bash")
+        machine.send_chars("touch  done4\n")
+        machine.wait_for_file("/home/alice/done4")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/pam/pam-u2f.nix b/nixpkgs/nixos/tests/pam/pam-u2f.nix
new file mode 100644
index 000000000000..46e307a3f125
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/pam-u2f.nix
@@ -0,0 +1,26 @@
+import ../make-test-python.nix ({ ... }:
+
+{
+  name = "pam-u2f";
+
+  nodes.machine =
+    { ... }:
+    {
+      security.pam.u2f = {
+        control = "required";
+        cue = true;
+        debug = true;
+        enable = true;
+        interactive = true;
+        origin = "nixos-test";
+      };
+    };
+
+  testScript =
+    ''
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed(
+          'egrep "auth required .*/lib/security/pam_u2f.so.*cue.*debug.*interactive.*origin=nixos-test" /etc/pam.d/ -R'
+      )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/pam/pam-ussh.nix b/nixpkgs/nixos/tests/pam/pam-ussh.nix
new file mode 100644
index 000000000000..ba0570dbf97d
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/pam-ussh.nix
@@ -0,0 +1,70 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  testOnlySSHCredentials = pkgs.runCommand "pam-ussh-test-ca" {
+    nativeBuildInputs = [ pkgs.openssh ];
+  } ''
+    mkdir $out
+    ssh-keygen -t ed25519 -N "" -f $out/ca
+
+    ssh-keygen -t ed25519 -N "" -f $out/alice
+    ssh-keygen -s $out/ca -I "alice user key" -n "alice,root" -V 19700101:forever $out/alice.pub
+
+    ssh-keygen -t ed25519 -N "" -f $out/bob
+    ssh-keygen -s $out/ca -I "bob user key" -n "bob" -V 19700101:forever $out/bob.pub
+  '';
+  makeTestScript = user: pkgs.writeShellScript "pam-ussh-${user}-test-script" ''
+    set -euo pipefail
+
+    eval $(${pkgs.openssh}/bin/ssh-agent)
+
+    mkdir -p $HOME/.ssh
+    chmod 700 $HOME/.ssh
+    cp ${testOnlySSHCredentials}/${user}{,.pub,-cert.pub} $HOME/.ssh
+    chmod 600 $HOME/.ssh/${user}
+    chmod 644 $HOME/.ssh/${user}{,-cert}.pub
+
+    set -x
+
+    ${pkgs.openssh}/bin/ssh-add $HOME/.ssh/${user}
+    ${pkgs.openssh}/bin/ssh-add -l &>2
+
+    exec sudo id -u -n
+  '';
+in {
+  name = "pam-ussh";
+  meta.maintainers = with lib.maintainers; [ lukegb ];
+
+  machine =
+    { ... }:
+    {
+      users.users.alice = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+      users.users.bob = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+
+      security.pam.ussh = {
+        enable = true;
+        authorizedPrincipals = "root";
+        caFile = "${testOnlySSHCredentials}/ca.pub";
+      };
+
+      security.sudo = {
+        enable = true;
+        extraConfig = ''
+          Defaults lecture="never"
+        '';
+      };
+    };
+
+  testScript =
+    ''
+      with subtest("alice should be allowed to escalate to root"):
+        machine.succeed(
+            'su -c "${makeTestScript "alice"}" -l alice | grep root'
+        )
+
+      with subtest("bob should not be allowed to escalate to root"):
+        machine.fail(
+            'su -c "${makeTestScript "bob"}" -l bob | grep root'
+        )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/pam/test_chfn.py b/nixpkgs/nixos/tests/pam/test_chfn.py
new file mode 100644
index 000000000000..3cfbb3908e9d
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/test_chfn.py
@@ -0,0 +1,28 @@
+expected_lines = {
+    "account required pam_unix.so",
+    "account sufficient @@pam_krb5@@/lib/security/pam_krb5.so",
+    "auth [default=die success=done] @@pam_ccreds@@/lib/security/pam_ccreds.so action=validate use_first_pass",
+    "auth [default=ignore success=1 service_err=reset] @@pam_krb5@@/lib/security/pam_krb5.so use_first_pass",
+    "auth required pam_deny.so",
+    "auth sufficient @@pam_ccreds@@/lib/security/pam_ccreds.so action=store use_first_pass",
+    "auth sufficient pam_rootok.so",
+    "auth sufficient pam_unix.so likeauth try_first_pass",
+    "password sufficient @@pam_krb5@@/lib/security/pam_krb5.so use_first_pass",
+    "password sufficient pam_unix.so nullok yescrypt",
+    "session optional @@pam_krb5@@/lib/security/pam_krb5.so",
+    "session required pam_env.so conffile=/etc/pam/environment readenv=0",
+    "session required pam_unix.so",
+}
+actual_lines = set(machine.succeed("cat /etc/pam.d/chfn").splitlines())
+
+stripped_lines = set([line.split("#")[0].rstrip() for line in actual_lines])
+missing_lines = expected_lines - stripped_lines
+extra_lines = stripped_lines - expected_lines
+non_functional_lines = set([line for line in extra_lines if line == ""])
+unexpected_functional_lines = extra_lines - non_functional_lines
+
+with subtest("All expected lines are in the file"):
+    assert not missing_lines, f"Missing lines: {missing_lines}"
+
+with subtest("All remaining lines are empty or comments"):
+    assert not unexpected_functional_lines, f"Unexpected lines: {unexpected_functional_lines}"
diff --git a/nixpkgs/nixos/tests/pam/zfs-key.nix b/nixpkgs/nixos/tests/pam/zfs-key.nix
new file mode 100644
index 000000000000..4f54c287e91a
--- /dev/null
+++ b/nixpkgs/nixos/tests/pam/zfs-key.nix
@@ -0,0 +1,83 @@
+import ../make-test-python.nix ({ ... }:
+
+  let
+    userPassword = "password";
+    mismatchPass = "mismatch";
+  in
+  {
+    name = "pam-zfs-key";
+
+    nodes.machine =
+      { ... }: {
+        boot.supportedFilesystems = [ "zfs" ];
+
+        networking.hostId = "12345678";
+
+        security.pam.zfs.enable = true;
+
+        users.users = {
+          alice = {
+            isNormalUser = true;
+            password = userPassword;
+          };
+          bob = {
+            isNormalUser = true;
+            password = userPassword;
+          };
+        };
+      };
+
+    testScript = { nodes, ... }:
+      let
+        homes = nodes.machine.security.pam.zfs.homes;
+        pool = builtins.head (builtins.split "/" homes);
+      in
+      ''
+        machine.wait_for_unit("multi-user.target")
+        machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+        with subtest("Create encrypted ZFS datasets"):
+          machine.succeed("truncate -s 64M /testpool.img")
+          machine.succeed("zpool create -O canmount=off '${pool}' /testpool.img")
+          machine.succeed("zfs create -o canmount=off -p '${homes}'")
+          machine.succeed("echo ${userPassword} | zfs create -o canmount=noauto -o encryption=on -o keyformat=passphrase '${homes}/alice'")
+          machine.succeed("zfs unload-key '${homes}/alice'")
+          machine.succeed("echo ${mismatchPass} | zfs create -o canmount=noauto -o encryption=on -o keyformat=passphrase '${homes}/bob'")
+          machine.succeed("zfs unload-key '${homes}/bob'")
+
+        with subtest("Switch to tty2"):
+          machine.fail("pgrep -f 'agetty.*tty2'")
+          machine.send_key("alt-f2")
+          machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+          machine.wait_for_unit("getty@tty2.service")
+          machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+
+        with subtest("Log in as user with home locked by login password"):
+          machine.wait_until_tty_matches("2", "login: ")
+          machine.send_chars("alice\n")
+          machine.wait_until_tty_matches("2", "login: alice")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("2", "Password: ")
+          machine.send_chars("${userPassword}\n")
+          machine.wait_until_succeeds("pgrep -u alice bash")
+          machine.succeed("mount | grep ${homes}/alice")
+
+        with subtest("Switch to tty3"):
+          machine.fail("pgrep -f 'agetty.*tty3'")
+          machine.send_key("alt-f3")
+          machine.wait_until_succeeds("[ $(fgconsole) = 3 ]")
+          machine.wait_for_unit("getty@tty3.service")
+          machine.wait_until_succeeds("pgrep -f 'agetty.*tty3'")
+
+        with subtest("Log in as user with home locked by password different from login"):
+          machine.wait_until_tty_matches("3", "login: ")
+          machine.send_chars("bob\n")
+          machine.wait_until_tty_matches("3", "login: bob")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("3", "Password: ")
+          machine.send_chars("${userPassword}\n")
+          machine.wait_until_succeeds("pgrep -u bob bash")
+          machine.fail("mount | grep ${homes}/bob")
+      '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/pantheon.nix b/nixpkgs/nixos/tests/pantheon.nix
new file mode 100644
index 000000000000..69a28c397bed
--- /dev/null
+++ b/nixpkgs/nixos/tests/pantheon.nix
@@ -0,0 +1,103 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+{
+  name = "pantheon";
+
+  meta.maintainers = lib.teams.pantheon.members;
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+
+    services.xserver.enable = true;
+    services.xserver.desktopManager.pantheon.enable = true;
+
+    environment.systemPackages = [ pkgs.xdotool ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    bob = nodes.machine.users.users.bob;
+  in ''
+    machine.wait_for_unit("display-manager.service")
+
+    with subtest("Test we can see usernames in elementary-greeter"):
+        machine.wait_for_text("${user.description}")
+        machine.wait_until_succeeds("pgrep -f io.elementary.greeter-compositor")
+        # OCR was struggling with this one.
+        # machine.wait_for_text("${bob.description}")
+        # Ensure the password box is focused by clicking it.
+        # Workaround for https://github.com/NixOS/nixpkgs/issues/211366.
+        machine.succeed("XAUTHORITY=/var/lib/lightdm/.Xauthority DISPLAY=:0 xdotool mousemove 512 505 click 1")
+        machine.sleep(2)
+        machine.screenshot("elementary_greeter_lightdm")
+
+    with subtest("Login with elementary-greeter"):
+        machine.send_chars("${user.password}\n")
+        machine.wait_for_x()
+        machine.wait_for_file("${user.home}/.Xauthority")
+        machine.succeed("xauth merge ${user.home}/.Xauthority")
+        machine.wait_until_succeeds('journalctl -t gnome-session-binary --grep "Entering running state"')
+
+    with subtest("Check that logging in has given the user ownership of devices"):
+        machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+    with subtest("Check if Pantheon components actually start"):
+        for i in ["gala", "io.elementary.wingpanel", "plank", "gsd-media-keys", "io.elementary.desktop.agent-polkit"]:
+            machine.wait_until_succeeds(f"pgrep -f {i}")
+        for i in ["gala", "io.elementary.wingpanel", "plank"]:
+            machine.wait_for_window(i)
+        machine.wait_for_unit("bamfdaemon.service", "${user.name}")
+        machine.wait_for_unit("io.elementary.files.xdg-desktop-portal.service", "${user.name}")
+
+    with subtest("Check if various environment variables are set"):
+        cmd = "xargs --null --max-args=1 echo < /proc/$(pgrep -xf /run/current-system/sw/bin/gala)/environ"
+        machine.succeed(f"{cmd} | grep 'XDG_CURRENT_DESKTOP' | grep 'Pantheon'")
+        # Hopefully from the sessionPath option.
+        machine.succeed(f"{cmd} | grep 'XDG_DATA_DIRS' | grep 'gsettings-schemas/pantheon-agent-geoclue2'")
+        # Hopefully from login shell.
+        machine.succeed(f"{cmd} | grep '__NIXOS_SET_ENVIRONMENT_DONE' | grep '1'")
+        # See elementary-session-settings packaging.
+        machine.succeed(f"{cmd} | grep 'XDG_CONFIG_DIRS' | grep 'elementary-default-settings'")
+
+    with subtest("Open elementary videos"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.videos >&2 &'")
+        machine.sleep(2)
+        machine.wait_for_window("io.elementary.videos")
+        machine.wait_for_text("No Videos Open")
+
+    with subtest("Open elementary calendar"):
+        machine.wait_until_succeeds("pgrep -f evolution-calendar-factory")
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.calendar >&2 &'")
+        machine.sleep(2)
+        machine.wait_for_window("io.elementary.calendar")
+
+    with subtest("Open system settings"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.switchboard >&2 &'")
+        # Wait for all plugins to be loaded before we check if the window is still there.
+        machine.sleep(5)
+        machine.wait_for_window("io.elementary.switchboard")
+
+    with subtest("Open elementary terminal"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.terminal >&2 &'")
+        machine.wait_for_window("io.elementary.terminal")
+
+    with subtest("Trigger multitasking view"):
+        cmd = "dbus-send --session --dest=org.pantheon.gala --print-reply /org/pantheon/gala org.pantheon.gala.PerformAction int32:1"
+        env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0"
+        machine.succeed(f"su - ${user.name} -c '{env} {cmd}'")
+        machine.sleep(3)
+        machine.screenshot("multitasking")
+        machine.succeed(f"su - ${user.name} -c '{env} {cmd}'")
+
+    with subtest("Check if gala has ever coredumped"):
+        machine.fail("coredumpctl --json=short | grep gala")
+        # So you can see the dock in the below screenshot.
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 xdotool mousemove 450 1000 >&2 &'")
+        machine.sleep(10)
+        machine.screenshot("screen")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/paperless.nix b/nixpkgs/nixos/tests/paperless.nix
new file mode 100644
index 000000000000..3d834b29958d
--- /dev/null
+++ b/nixpkgs/nixos/tests/paperless.nix
@@ -0,0 +1,89 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "paperless";
+  meta.maintainers = with lib.maintainers; [ erikarvstedt Flakebi ];
+
+  nodes = let self = {
+    simple = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ imagemagick jq ];
+      services.paperless = {
+        enable = true;
+        passwordFile = builtins.toFile "password" "admin";
+      };
+    };
+    postgres = { config, pkgs, ... }: {
+      imports = [ self.simple ];
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "paperless" ];
+        ensureUsers = [
+          { name = config.services.paperless.user;
+            ensureDBOwnership = true;
+          }
+        ];
+      };
+      services.paperless.settings = {
+        PAPERLESS_DBHOST = "/run/postgresql";
+      };
+    };
+  }; in self;
+
+  testScript = ''
+    import json
+
+    def test_paperless(node):
+      node.wait_for_unit("paperless-consumer.service")
+
+      with subtest("Add a document via the file system"):
+        node.succeed(
+          "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
+          "-annotate +5+20 'hello world 16-10-2005' /var/lib/paperless/consume/doc.png"
+        )
+
+      with subtest("Web interface gets ready"):
+        node.wait_for_unit("paperless-web.service")
+        # Wait until server accepts connections
+        node.wait_until_succeeds("curl -fs localhost:28981")
+
+      # Required for consuming documents via the web interface
+      with subtest("Task-queue gets ready"):
+        node.wait_for_unit("paperless-task-queue.service")
+
+      with subtest("Add a png document via the web interface"):
+        node.succeed(
+          "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
+          "-annotate +5+20 'hello web 16-10-2005' /tmp/webdoc.png"
+        )
+        node.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.png -fs localhost:28981/api/documents/post_document/")
+
+      with subtest("Add a txt document via the web interface"):
+        node.succeed(
+          "echo 'hello web 16-10-2005' > /tmp/webdoc.txt"
+        )
+        node.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.txt -fs localhost:28981/api/documents/post_document/")
+
+      with subtest("Documents are consumed"):
+        node.wait_until_succeeds(
+          "(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 3))"
+        )
+        docs = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/"))['results']
+        assert "2005-10-16" in docs[0]['created']
+        assert "2005-10-16" in docs[1]['created']
+        assert "2005-10-16" in docs[2]['created']
+
+      # Detects gunicorn issues, see PR #190888
+      with subtest("Document metadata can be accessed"):
+        metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/1/metadata/"))
+        assert "original_checksum" in metadata
+
+        metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/2/metadata/"))
+        assert "original_checksum" in metadata
+
+        metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/3/metadata/"))
+        assert "original_checksum" in metadata
+
+    test_paperless(simple)
+    simple.send_monitor_command("quit")
+    simple.wait_for_shutdown()
+    test_paperless(postgres)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/parsedmarc/default.nix b/nixpkgs/nixos/tests/parsedmarc/default.nix
new file mode 100644
index 000000000000..1feadcb7f39b
--- /dev/null
+++ b/nixpkgs/nixos/tests/parsedmarc/default.nix
@@ -0,0 +1,230 @@
+# This tests parsedmarc by sending a report to its monitored email
+# address and reading the results out of Elasticsearch.
+
+{ pkgs, ... }@args:
+let
+  inherit (import ../../lib/testing-python.nix args) makeTest;
+  inherit (pkgs) lib;
+
+  dmarcTestReport = builtins.fetchurl {
+    name = "dmarc-test-report";
+    url = "https://github.com/domainaware/parsedmarc/raw/f45ab94e0608088e0433557608d9f4e9517d3afe/samples/aggregate/estadocuenta1.infonacot.gob.mx!example.com!1536853302!1536939702!2940.xml.zip";
+    sha256 = "0dq64cj49711kbja27pjl2hy0d3azrjxg91kqrh40x46fkn1dwkx";
+  };
+
+  sendEmail = address:
+    pkgs.writeScriptBin "send-email" ''
+      #!${pkgs.python3.interpreter}
+      import smtplib
+      from email import encoders
+      from email.mime.base import MIMEBase
+      from email.mime.multipart import MIMEMultipart
+      from email.mime.text import MIMEText
+
+      sender_email = "dmarc_tester@fake.domain"
+      receiver_email = "${address}"
+
+      message = MIMEMultipart()
+      message["From"] = sender_email
+      message["To"] = receiver_email
+      message["Subject"] = "DMARC test"
+
+      message.attach(MIMEText("Testing parsedmarc", "plain"))
+
+      attachment = MIMEBase("application", "zip")
+
+      with open("${dmarcTestReport}", "rb") as report:
+          attachment.set_payload(report.read())
+
+      encoders.encode_base64(attachment)
+
+      attachment.add_header(
+          "Content-Disposition",
+          "attachment; filename= estadocuenta1.infonacot.gob.mx!example.com!1536853302!1536939702!2940.xml.zip",
+      )
+
+      message.attach(attachment)
+      text = message.as_string()
+
+      with smtplib.SMTP('localhost') as server:
+          server.sendmail(sender_email, receiver_email, text)
+          server.quit()
+    '';
+in
+{
+  localMail = makeTest
+    {
+      name = "parsedmarc-local-mail";
+      meta = with lib.maintainers; {
+        maintainers = [ talyz ];
+      };
+
+      nodes.parsedmarc =
+        { nodes, ... }:
+        {
+          virtualisation.memorySize = 2048;
+
+          services.postfix = {
+            enableSubmission = true;
+            enableSubmissions = true;
+            submissionsOptions = {
+              smtpd_sasl_auth_enable = "yes";
+              smtpd_client_restrictions = "permit";
+            };
+          };
+
+          services.parsedmarc = {
+            enable = true;
+            provision = {
+              geoIp = false;
+              localMail = {
+                enable = true;
+                hostname = "localhost";
+              };
+            };
+          };
+
+          environment.systemPackages = [
+            (sendEmail "dmarc@localhost")
+            pkgs.jq
+          ];
+        };
+
+      testScript = { nodes }:
+        let
+          esPort = toString nodes.parsedmarc.config.services.elasticsearch.port;
+          valueObject = lib.optionalString (lib.versionAtLeast nodes.parsedmarc.config.services.elasticsearch.package.version "7") ".value";
+        in ''
+          parsedmarc.start()
+          parsedmarc.wait_for_unit("postfix.service")
+          parsedmarc.wait_for_unit("dovecot2.service")
+          parsedmarc.wait_for_unit("parsedmarc.service")
+          parsedmarc.wait_until_succeeds(
+              "curl -sS -f http://localhost:${esPort}"
+          )
+
+          parsedmarc.fail(
+              "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
+          )
+          parsedmarc.succeed("send-email")
+          parsedmarc.wait_until_succeeds(
+              "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
+              + " | tee /dev/console"
+              + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
+          )
+        '';
+    };
+
+  externalMail =
+    let
+      certs = import ../common/acme/server/snakeoil-certs.nix;
+      mailDomain = certs.domain;
+      parsedmarcDomain = "parsedmarc.fake.domain";
+    in
+      makeTest {
+        name = "parsedmarc-external-mail";
+        meta = with lib.maintainers; {
+          maintainers = [ talyz ];
+        };
+
+        nodes = {
+          parsedmarc =
+            { nodes, ... }:
+            {
+              virtualisation.memorySize = 2048;
+
+              security.pki.certificateFiles = [
+                certs.ca.cert
+              ];
+
+              networking.extraHosts = ''
+                127.0.0.1 ${parsedmarcDomain}
+                ${nodes.mail.config.networking.primaryIPAddress} ${mailDomain}
+              '';
+
+              services.parsedmarc = {
+                enable = true;
+                provision.geoIp = false;
+                settings.imap = {
+                  host = mailDomain;
+                  port = 993;
+                  ssl = true;
+                  user = "alice";
+                  password = "${pkgs.writeText "imap-password" "foobar"}";
+                };
+              };
+
+              environment.systemPackages = [
+                pkgs.jq
+              ];
+            };
+
+          mail =
+            { nodes, ... }:
+            {
+              imports = [ ../common/user-account.nix ];
+
+              networking.extraHosts = ''
+                127.0.0.1 ${mailDomain}
+                ${nodes.parsedmarc.config.networking.primaryIPAddress} ${parsedmarcDomain}
+              '';
+
+              services.dovecot2 = {
+                enable = true;
+                protocols = [ "imap" ];
+                sslCACert = "${certs.ca.cert}";
+                sslServerCert = "${certs.${mailDomain}.cert}";
+                sslServerKey = "${certs.${mailDomain}.key}";
+              };
+
+              services.postfix = {
+                enable = true;
+                origin = mailDomain;
+                config = {
+                  myhostname = mailDomain;
+                  mydestination = mailDomain;
+                };
+                enableSubmission = true;
+                enableSubmissions = true;
+                submissionsOptions = {
+                  smtpd_sasl_auth_enable = "yes";
+                  smtpd_client_restrictions = "permit";
+                };
+              };
+              environment.systemPackages = [ (sendEmail "alice@${mailDomain}") ];
+
+              networking.firewall.allowedTCPPorts = [ 993 ];
+            };
+        };
+
+        testScript = { nodes }:
+          let
+            esPort = toString nodes.parsedmarc.config.services.elasticsearch.port;
+            valueObject = lib.optionalString (lib.versionAtLeast nodes.parsedmarc.config.services.elasticsearch.package.version "7") ".value";
+          in ''
+            mail.start()
+            mail.wait_for_unit("postfix.service")
+            mail.wait_for_unit("dovecot2.service")
+
+            parsedmarc.start()
+            parsedmarc.wait_for_unit("parsedmarc.service")
+            parsedmarc.wait_until_succeeds(
+                "curl -sS -f http://localhost:${esPort}"
+            )
+
+            parsedmarc.fail(
+                "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
+                + " | tee /dev/console"
+                + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
+            )
+            mail.succeed("send-email")
+            parsedmarc.wait_until_succeeds(
+                "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
+                + " | tee /dev/console"
+                + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
+            )
+          '';
+      };
+}
diff --git a/nixpkgs/nixos/tests/pass-secret-service.nix b/nixpkgs/nixos/tests/pass-secret-service.nix
new file mode 100644
index 000000000000..e0dddf0ad29e
--- /dev/null
+++ b/nixpkgs/nixos/tests/pass-secret-service.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "pass-secret-service";
+  meta.maintainers = [ lib.maintainers.aidalgol ];
+
+  nodes.machine = { nodes, pkgs, ... }:
+    {
+      imports = [ ./common/user-account.nix ];
+
+      services.passSecretService.enable = true;
+
+      environment.systemPackages = [
+        # Create a script that tries to make a request to the D-Bus secrets API.
+        (pkgs.writers.writePython3Bin "secrets-dbus-init"
+          {
+            libraries = [ pkgs.python3Packages.secretstorage ];
+          } ''
+          import secretstorage
+          print("Initializing dbus connection...")
+          connection = secretstorage.dbus_init()
+          print("Requesting default collection...")
+          collection = secretstorage.get_default_collection(connection)
+          print("Done!  dbus-org.freedesktop.secrets should now be active.")
+        '')
+        pkgs.pass
+      ];
+
+      programs.gnupg = {
+        agent.enable = true;
+        agent.pinentryFlavor = "tty";
+        dirmngr.enable = true;
+      };
+    };
+
+  # Some of the commands are run via a virtual console because they need to be
+  # run under a real login session, with D-Bus running in the environment.
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.config.users.users.alice;
+      gpg-uid = "alice@example.net";
+      gpg-pw = "foobar9000";
+      ready-file = "/tmp/secrets-dbus-init.done";
+    in
+    ''
+      # Initialise the pass(1) storage.
+      machine.succeed("""
+        sudo -u alice gpg --pinentry-mode loopback --batch --passphrase ${gpg-pw} \
+        --quick-gen-key ${gpg-uid} \
+      """)
+      machine.succeed("sudo -u alice pass init ${gpg-uid}")
+
+      with subtest("Service is not running on login"):
+          machine.wait_until_tty_matches("1", "login: ")
+          machine.send_chars("alice\n")
+          machine.wait_until_tty_matches("1", "login: alice")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("1", "Password: ")
+          machine.send_chars("${user.password}\n")
+          machine.wait_until_succeeds("pgrep -u alice bash")
+
+          _, output = machine.systemctl("status dbus-org.freedesktop.secrets --no-pager", "alice")
+          assert "Active: inactive (dead)" in output
+
+      with subtest("Service starts after a client tries to talk to the D-Bus API"):
+          machine.send_chars("secrets-dbus-init; touch ${ready-file}\n")
+          machine.wait_for_file("${ready-file}")
+          _, output = machine.systemctl("status dbus-org.freedesktop.secrets --no-pager", "alice")
+          assert "Active: active (running)" in output
+    '';
+})
diff --git a/nixpkgs/nixos/tests/patroni.nix b/nixpkgs/nixos/tests/patroni.nix
new file mode 100644
index 000000000000..1f15cd59677a
--- /dev/null
+++ b/nixpkgs/nixos/tests/patroni.nix
@@ -0,0 +1,206 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+  let
+    nodesIps = [
+      "192.168.1.1"
+      "192.168.1.2"
+      "192.168.1.3"
+    ];
+
+    createNode = index: { pkgs, ... }:
+      let
+        ip = builtins.elemAt nodesIps index; # since we already use IPs to identify servers
+      in
+      {
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = ip; prefixLength = 16; }
+        ];
+
+        networking.firewall.allowedTCPPorts = [ 5432 8008 5010 ];
+
+        environment.systemPackages = [ pkgs.jq ];
+
+        services.patroni = {
+
+          enable = true;
+
+          postgresqlPackage = pkgs.postgresql_14.withPackages (p: [ p.pg_safeupdate ]);
+
+          scope = "cluster1";
+          name = "node${toString(index + 1)}";
+          nodeIp = ip;
+          otherNodesIps = builtins.filter (h: h != ip) nodesIps;
+          softwareWatchdog = true;
+
+          settings = {
+            bootstrap = {
+              dcs = {
+                ttl = 30;
+                loop_wait = 10;
+                retry_timeout = 10;
+                maximum_lag_on_failover = 1048576;
+              };
+              initdb = [
+                { encoding = "UTF8"; }
+                "data-checksums"
+              ];
+            };
+
+            postgresql = {
+              use_pg_rewind = true;
+              use_slots = true;
+              authentication = {
+                replication = {
+                  username = "replicator";
+                };
+                superuser = {
+                  username = "postgres";
+                };
+                rewind = {
+                  username = "rewind";
+                };
+              };
+              parameters = {
+                listen_addresses = "${ip}";
+                wal_level = "replica";
+                hot_standby_feedback = "on";
+                unix_socket_directories = "/tmp";
+              };
+              pg_hba = [
+                "host replication replicator 192.168.1.0/24 md5"
+                # Unsafe, do not use for anything other than tests
+                "host all all 0.0.0.0/0 trust"
+              ];
+            };
+
+            etcd3 = {
+              host = "192.168.1.4:2379";
+            };
+          };
+
+          environmentFiles = {
+            PATRONI_REPLICATION_PASSWORD = pkgs.writeText "replication-password" "postgres";
+            PATRONI_SUPERUSER_PASSWORD = pkgs.writeText "superuser-password" "postgres";
+            PATRONI_REWIND_PASSWORD = pkgs.writeText "rewind-password" "postgres";
+          };
+        };
+
+        # We always want to restart so the tests never hang
+        systemd.services.patroni.serviceConfig.StartLimitIntervalSec = 0;
+      };
+  in
+  {
+    name = "patroni";
+
+    nodes = {
+      node1 = createNode 0;
+      node2 = createNode 1;
+      node3 = createNode 2;
+
+      etcd = { pkgs, ... }: {
+
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.1.4"; prefixLength = 16; }
+        ];
+
+        services.etcd = {
+          enable = true;
+          listenClientUrls = [ "http://192.168.1.4:2379" ];
+        };
+
+        networking.firewall.allowedTCPPorts = [ 2379 ];
+      };
+
+      client = { pkgs, ... }: {
+        environment.systemPackages = [ pkgs.postgresql_14 ];
+
+        networking.interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+          { address = "192.168.2.1"; prefixLength = 16; }
+        ];
+
+        services.haproxy = {
+          enable = true;
+          config = ''
+            global
+                maxconn 100
+
+            defaults
+                log global
+                mode tcp
+                retries 2
+                timeout client 30m
+                timeout connect 4s
+                timeout server 30m
+                timeout check 5s
+
+            listen cluster1
+                bind 127.0.0.1:5432
+                option httpchk
+                http-check expect status 200
+                default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
+                ${builtins.concatStringsSep "\n" (map (ip: "server postgresql_${ip}_5432 ${ip}:5432 maxconn 100 check port 8008") nodesIps)}
+          '';
+        };
+      };
+    };
+
+
+
+    testScript = ''
+      nodes = [node1, node2, node3]
+
+      def wait_for_all_nodes_ready(expected_replicas=2):
+          booted_nodes = filter(lambda node: node.booted, nodes)
+          for node in booted_nodes:
+              print(node.succeed("patronictl list cluster1"))
+              node.wait_until_succeeds(f"[ $(patronictl list -f json cluster1 | jq 'length') == {expected_replicas + 1} ]")
+              node.wait_until_succeeds("[ $(patronictl list -f json cluster1 | jq 'map(select(.Role | test(\"^Leader$\"))) | map(select(.State | test(\"^running$\"))) | length') == 1 ]")
+              node.wait_until_succeeds(f"[ $(patronictl list -f json cluster1 | jq 'map(select(.Role | test(\"^Replica$\"))) | map(select(.State | test(\"^running$\"))) | length') == {expected_replicas} ]")
+              print(node.succeed("patronictl list cluster1"))
+          client.wait_until_succeeds("psql -h 127.0.0.1 -U postgres --command='select 1;'")
+
+      def run_dummy_queries():
+          client.succeed("psql -h 127.0.0.1 -U postgres --pset='pager=off' --tuples-only --command='insert into dummy(val) values (101);'")
+          client.succeed("test $(psql -h 127.0.0.1 -U postgres --pset='pager=off' --tuples-only --command='select val from dummy where val = 101;') -eq 101")
+          client.succeed("psql -h 127.0.0.1 -U postgres --pset='pager=off' --tuples-only --command='delete from dummy where val = 101;'")
+
+      start_all()
+
+      etcd.wait_for_unit("etcd.service")
+
+      with subtest("should bootstrap a new patroni cluster"):
+          wait_for_all_nodes_ready()
+
+      with subtest("should be able to insert and select"):
+          client.succeed("psql -h 127.0.0.1 -U postgres --command='create table dummy as select * from generate_series(1, 100) as val;'")
+          client.succeed("test $(psql -h 127.0.0.1 -U postgres --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100")
+
+      with subtest("should restart after all nodes are crashed"):
+          for node in nodes:
+              node.crash()
+          for node in nodes:
+              node.start()
+          wait_for_all_nodes_ready()
+
+      with subtest("should be able to run queries while any one node is crashed"):
+          masterNodeName = node1.succeed("patronictl list -f json cluster1 | jq '.[] | select(.Role | test(\"^Leader$\")) | .Member' -r").strip()
+          masterNodeIndex = int(masterNodeName[len(masterNodeName)-1]) - 1
+
+          # Move master node at the end of the list to avoid multiple failovers (makes the test faster and more consistent)
+          nodes.append(nodes.pop(masterNodeIndex))
+
+          for node in nodes:
+              node.crash()
+              wait_for_all_nodes_ready(1)
+
+              # Execute some queries while a node is down.
+              run_dummy_queries()
+
+              # Restart crashed node.
+              node.start()
+              wait_for_all_nodes_ready()
+
+              # Execute some queries with the node back up.
+              run_dummy_queries()
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/pdns-recursor.nix b/nixpkgs/nixos/tests/pdns-recursor.nix
new file mode 100644
index 000000000000..14f1b7ea8a35
--- /dev/null
+++ b/nixpkgs/nixos/tests/pdns-recursor.nix
@@ -0,0 +1,15 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "powerdns-recursor";
+
+  nodes.server = { ... }: {
+    services.pdns-recursor.enable = true;
+    services.pdns-recursor.exportHosts= true;
+    networking.hosts."192.0.2.1" = [ "example.com" ];
+  };
+
+  testScript = ''
+    server.wait_for_unit("pdns-recursor")
+    server.wait_for_open_port(53)
+    assert "192.0.2.1" in server.succeed("host example.com localhost")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/peerflix.nix b/nixpkgs/nixos/tests/peerflix.nix
new file mode 100644
index 000000000000..4800413783b1
--- /dev/null
+++ b/nixpkgs/nixos/tests/peerflix.nix
@@ -0,0 +1,23 @@
+# This test runs peerflix and checks if peerflix starts
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "peerflix";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ offline ];
+  };
+
+  nodes = {
+    peerflix =
+      { ... }:
+        {
+          services.peerflix.enable = true;
+        };
+    };
+
+  testScript = ''
+    start_all()
+
+    peerflix.wait_for_unit("peerflix.service")
+    peerflix.wait_until_succeeds("curl -f localhost:9000")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/peroxide.nix b/nixpkgs/nixos/tests/peroxide.nix
new file mode 100644
index 000000000000..12e196484164
--- /dev/null
+++ b/nixpkgs/nixos/tests/peroxide.nix
@@ -0,0 +1,16 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "peroxide";
+  meta.maintainers = with lib.maintainers; [ aidalgol ];
+
+  nodes.machine =
+    { config, pkgs, ... }: {
+      networking.hostName = "nixos";
+      services.peroxide.enable = true;
+    };
+
+  testScript = ''
+    machine.wait_for_unit("peroxide.service")
+    machine.wait_for_open_port(1143) # IMAP
+    machine.wait_for_open_port(1025) # SMTP
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pgadmin4.nix b/nixpkgs/nixos/tests/pgadmin4.nix
new file mode 100644
index 000000000000..b726a3eb3b94
--- /dev/null
+++ b/nixpkgs/nixos/tests/pgadmin4.nix
@@ -0,0 +1,80 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "pgadmin4";
+  meta.maintainers = with lib.maintainers; [ mkg20001 gador ];
+
+  nodes = {
+    machine = { pkgs, ... }: {
+
+      imports = [ ./common/user-account.nix ];
+
+      environment.systemPackages = with pkgs; [
+        wget
+        curl
+        pgadmin4-desktopmode
+      ];
+
+      services.postgresql = {
+        enable = true;
+        authentication = ''
+          host    all             all             localhost               trust
+        '';
+      };
+
+      services.pgadmin = {
+        port = 5051;
+        enable = true;
+        initialEmail = "bruh@localhost.de";
+        initialPasswordFile = pkgs.writeText "pw" "bruh2012!";
+      };
+    };
+    machine2 = { pkgs, ... }: {
+
+      imports = [ ./common/user-account.nix ];
+
+      services.postgresql = {
+        enable = true;
+      };
+
+      services.pgadmin = {
+        enable = true;
+        initialEmail = "bruh@localhost.de";
+        initialPasswordFile = pkgs.writeText "pw" "bruh2012!";
+        minimumPasswordLength = 12;
+      };
+    };
+  };
+
+
+  testScript = ''
+    with subtest("Check pgadmin module"):
+      machine.wait_for_unit("postgresql")
+      machine.wait_for_unit("pgadmin")
+      machine.wait_until_succeeds("curl -sS localhost:5051")
+      machine.wait_until_succeeds("curl -sS localhost:5051/login | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+      # check for missing support files (css, js etc). Should catch not-generated files during build. See e.g. https://github.com/NixOS/nixpkgs/pull/229184
+      machine.succeed("wget -nv --level=1 --spider --recursive localhost:5051/login")
+      # test idempotenceny
+      machine.systemctl("restart pgadmin.service")
+      machine.wait_for_unit("pgadmin")
+      machine.wait_until_succeeds("curl -sS localhost:5051")
+      machine.wait_until_succeeds("curl -sS localhost:5051/login | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+
+    # pgadmin4 module saves the configuration to /etc/pgadmin/config_system.py
+    # pgadmin4-desktopmode tries to read that as well. This normally fails with a PermissionError, as the config file
+    # is owned by the user of the pgadmin module. With the check-system-config-dir.patch this will just throw a warning
+    # but will continue and not read the file.
+    # If we run pgadmin4-desktopmode as root (something one really shouldn't do), it can read the config file and fail,
+    # because of the wrong config for desktopmode.
+    with subtest("Check pgadmin standalone desktop mode"):
+      machine.execute("sudo -u alice pgadmin4 >&2 &", timeout=60)
+      machine.wait_until_succeeds("curl -sS localhost:5050")
+      machine.wait_until_succeeds("curl -sS localhost:5050/browser/ | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+      machine.succeed("wget -nv --level=1 --spider --recursive localhost:5050/browser")
+
+    with subtest("Check pgadmin minimum password length"):
+      machine2.wait_for_unit("postgresql")
+      machine2.wait_for_console_text("Password must be at least 12 characters long")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pgbouncer.nix b/nixpkgs/nixos/tests/pgbouncer.nix
new file mode 100644
index 000000000000..bb5afd35ee28
--- /dev/null
+++ b/nixpkgs/nixos/tests/pgbouncer.nix
@@ -0,0 +1,59 @@
+import ./make-test-python.nix ({ pkgs, ... } :
+let
+  testAuthFile = pkgs.writeTextFile {
+    name = "authFile";
+    text = ''
+      "testuser" "testpass"
+    '';
+  };
+in
+{
+  name = "pgbouncer";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ _1000101 ];
+  };
+  nodes = {
+    one = { config, pkgs, ... }: {
+
+      systemd.services.postgresql = {
+        postStart = ''
+          ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER ROLE testuser WITH LOGIN PASSWORD 'testpass'";
+          ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER DATABASE testdb OWNER TO testuser;";
+        '';
+      };
+
+      services = {
+        postgresql = {
+          enable = true;
+          ensureDatabases = [ "testdb" ];
+          ensureUsers = [
+          {
+            name = "testuser";
+          }];
+          authentication = ''
+            local testdb testuser scram-sha-256
+          '';
+        };
+
+        pgbouncer = {
+          enable = true;
+          listenAddress = "localhost";
+          databases = { test = "host=/run/postgresql/ port=5432 auth_user=testuser dbname=testdb"; };
+          authType = "scram-sha-256";
+          authFile = testAuthFile;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    one.wait_for_unit("default.target")
+    one.require_unit_state("pgbouncer.service", "active")
+
+    # Test if we can make a query through PgBouncer
+    one.wait_until_succeeds(
+        "psql 'postgres://testuser:testpass@localhost:6432/test' -c 'SELECT 1;'"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pgjwt.nix b/nixpkgs/nixos/tests/pgjwt.nix
new file mode 100644
index 000000000000..8d3310b74eb3
--- /dev/null
+++ b/nixpkgs/nixos/tests/pgjwt.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+
+with pkgs; {
+  name = "pgjwt";
+  meta = with lib.maintainers; {
+    maintainers = [ spinus willibutz ];
+  };
+
+  nodes = {
+    master = { ... }:
+    {
+      services.postgresql = {
+        enable = true;
+        extraPlugins = ps: with ps; [ pgjwt pgtap ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+  let
+    sqlSU = "${nodes.master.config.services.postgresql.superUser}";
+    pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}";
+  in
+  ''
+    start_all()
+    master.wait_for_unit("postgresql")
+    master.succeed(
+        "${pkgs.gnused}/bin/sed -e '12 i CREATE EXTENSION pgcrypto;\\nCREATE EXTENSION pgtap;\\nSET search_path TO tap,public;' ${pgjwt.src}/test.sql > /tmp/test.sql"
+    )
+    master.succeed(
+        "${pkgs.sudo}/bin/sudo -u ${sqlSU} PGOPTIONS=--search_path=tap,public ${pgProve}/bin/pg_prove -d postgres -v -f /tmp/test.sql"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pgmanage.nix b/nixpkgs/nixos/tests/pgmanage.nix
new file mode 100644
index 000000000000..6f8f2f965340
--- /dev/null
+++ b/nixpkgs/nixos/tests/pgmanage.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix ({ pkgs, ... } :
+let
+  role     = "test";
+  password = "secret";
+  conn     = "local";
+in
+{
+  name = "pgmanage";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ basvandijk ];
+  };
+  nodes = {
+    one = { config, pkgs, ... }: {
+      services = {
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script" ''
+            CREATE ROLE ${role} SUPERUSER LOGIN PASSWORD '${password}';
+          '';
+        };
+        pgmanage = {
+          enable = true;
+          connections = {
+            ${conn} = "hostaddr=127.0.0.1 port=${toString config.services.postgresql.port} dbname=postgres";
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    one.wait_for_unit("default.target")
+    one.require_unit_state("pgmanage.service", "active")
+
+    # Test if we can log in.
+    one.wait_until_succeeds(
+        "curl 'http://localhost:8080/pgmanage/auth' --data 'action=login&connname=${conn}&username=${role}&password=${password}' --fail"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/phosh.nix b/nixpkgs/nixos/tests/phosh.nix
new file mode 100644
index 000000000000..78d6da31beee
--- /dev/null
+++ b/nixpkgs/nixos/tests/phosh.nix
@@ -0,0 +1,70 @@
+import ./make-test-python.nix ({ pkgs, ...}: let
+  pin = "1234";
+in {
+  name = "phosh";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tomfitzhenry zhaofengli ];
+  };
+
+  nodes = {
+    phone = { config, pkgs, ... }: {
+      users.users.nixos = {
+        isNormalUser = true;
+        password = pin;
+      };
+
+      services.xserver.desktopManager.phosh = {
+        enable = true;
+        user = "nixos";
+        group = "users";
+
+        phocConfig = {
+          outputs.Virtual-1 = {
+            scale = 2;
+          };
+        };
+      };
+
+      systemd.services.phosh = {
+        environment = {
+          # Accelerated graphics fail on phoc 0.20 (wlroots 0.15)
+          "WLR_RENDERER" = "pixman";
+        };
+      };
+
+      virtualisation.resolution = { x = 720; y = 1440; };
+      virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci,xres=720,yres=1440" ];
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    import time
+
+    start_all()
+    phone.wait_for_unit("phosh.service")
+
+    with subtest("Check that we can see the lock screen info page"):
+        # Saturday, January 1
+        phone.succeed("timedatectl set-time '2022-01-01 07:00'")
+
+        phone.wait_for_text("Saturday")
+        phone.screenshot("01lockinfo")
+
+    with subtest("Check that we can unlock the screen"):
+        phone.send_chars("${pin}", delay=0.2)
+        time.sleep(1)
+        phone.screenshot("02unlock")
+
+        phone.send_chars("\n")
+
+        phone.wait_for_text("All Apps")
+        phone.screenshot("03launcher")
+
+    with subtest("Check the on-screen keyboard shows"):
+        phone.send_chars("setting", delay=0.2)
+        phone.wait_for_text("123") # A button on the OSK
+        phone.screenshot("04osk")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/photoprism.nix b/nixpkgs/nixos/tests/photoprism.nix
new file mode 100644
index 000000000000..a77ab59f5c9a
--- /dev/null
+++ b/nixpkgs/nixos/tests/photoprism.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "photoprism";
+  meta.maintainers = with lib.maintainers; [ stunkymonkey ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.photoprism = {
+      enable = true;
+      port = 8080;
+      originalsPath = "/media/photos/";
+      passwordFile = pkgs.writeText "password" "secret";
+    };
+    environment.extraInit = ''
+      mkdir -p /media/photos
+    '';
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(8080)
+    response = machine.succeed("curl -vvv -s -H 'Host: photoprism' http://127.0.0.1:8080/library/login")
+    assert '<title>PhotoPrism</title>' in response, "Login page didn't load successfully"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/php/default.nix b/nixpkgs/nixos/tests/php/default.nix
new file mode 100644
index 000000000000..c0386385753f
--- /dev/null
+++ b/nixpkgs/nixos/tests/php/default.nix
@@ -0,0 +1,16 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+, php ? pkgs.php
+}:
+
+let
+  php' = php.buildEnv {
+    extensions = { enabled, all }: with all; enabled ++ [ apcu ];
+  };
+in
+{
+  fpm = import ./fpm.nix { inherit system pkgs; php = php'; };
+  httpd = import ./httpd.nix { inherit system pkgs; php = php'; };
+  pcre = import ./pcre.nix { inherit system pkgs; php = php'; };
+}
diff --git a/nixpkgs/nixos/tests/php/fpm.nix b/nixpkgs/nixos/tests/php/fpm.nix
new file mode 100644
index 000000000000..64b61a377e28
--- /dev/null
+++ b/nixpkgs/nixos/tests/php/fpm.nix
@@ -0,0 +1,59 @@
+import ../make-test-python.nix ({ pkgs, lib, php, ... }: {
+  name = "php-${php.version}-fpm-nginx-test";
+  meta.maintainers = lib.teams.php.members;
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    environment.systemPackages = [ php ];
+
+    services.nginx = {
+      enable = true;
+
+      virtualHosts."phpfpm" =
+        let
+          testdir = pkgs.writeTextDir "web/index.php" "<?php phpinfo();";
+        in
+        {
+          root = "${testdir}/web";
+          locations."~ \\.php$".extraConfig = ''
+            fastcgi_pass unix:${config.services.phpfpm.pools.foobar.socket};
+            fastcgi_index index.php;
+            include ${config.services.nginx.package}/conf/fastcgi_params;
+            include ${pkgs.nginx}/conf/fastcgi.conf;
+          '';
+          locations."/" = {
+            tryFiles = "$uri $uri/ index.php";
+            index = "index.php index.html index.htm";
+          };
+        };
+    };
+
+    services.phpfpm.pools."foobar" = {
+      user = "nginx";
+      phpPackage = php;
+      settings = {
+        "listen.group" = "nginx";
+        "listen.mode" = "0600";
+        "listen.owner" = "nginx";
+        "pm" = "dynamic";
+        "pm.max_children" = 5;
+        "pm.max_requests" = 500;
+        "pm.max_spare_servers" = 3;
+        "pm.min_spare_servers" = 1;
+        "pm.start_servers" = 2;
+      };
+    };
+  };
+  testScript = { ... }: ''
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_unit("phpfpm-foobar.service")
+
+    # Check so we get an evaluated PHP back
+    response = machine.succeed("curl -fvvv -s http://127.0.0.1:80/")
+    assert "PHP Version ${php.version}" in response, "PHP version not detected"
+
+    # Check so we have database and some other extensions loaded
+    for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite", "apcu"]:
+        assert ext in response, f"Missing {ext} extension"
+        machine.succeed(f'test -n "$(php -m | grep -i {ext})"')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/php/httpd.nix b/nixpkgs/nixos/tests/php/httpd.nix
new file mode 100644
index 000000000000..b6dfbeeaed52
--- /dev/null
+++ b/nixpkgs/nixos/tests/php/httpd.nix
@@ -0,0 +1,34 @@
+import ../make-test-python.nix ({ pkgs, lib, php, ... }: {
+  name = "php-${php.version}-httpd-test";
+  meta.maintainers = lib.teams.php.members;
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    services.httpd = {
+      enable = true;
+      adminAddr = "admin@phpfpm";
+      virtualHosts."phpfpm" =
+        let
+          testdir = pkgs.writeTextDir "web/index.php" "<?php phpinfo();";
+        in
+        {
+          documentRoot = "${testdir}/web";
+          locations."/" = {
+            index = "index.php index.html";
+          };
+        };
+      phpPackage = php;
+      enablePHP = true;
+    };
+  };
+  testScript = { ... }: ''
+    machine.wait_for_unit("httpd.service")
+
+    # Check so we get an evaluated PHP back
+    response = machine.succeed("curl -fvvv -s http://127.0.0.1:80/")
+    assert "PHP Version ${php.version}" in response, "PHP version not detected"
+
+    # Check so we have database and some other extensions loaded
+    for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite"]:
+        assert ext in response, f"Missing {ext} extension"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/php/pcre.nix b/nixpkgs/nixos/tests/php/pcre.nix
new file mode 100644
index 000000000000..8e37d5dcf97b
--- /dev/null
+++ b/nixpkgs/nixos/tests/php/pcre.nix
@@ -0,0 +1,52 @@
+let
+  testString = "can-use-subgroups";
+in
+import ../make-test-python.nix ({ pkgs, lib, php, ... }: {
+  name = "php-${php.version}-httpd-pcre-jit-test";
+  meta.maintainers = lib.teams.php.members;
+
+  nodes.machine = { lib, pkgs, ... }: {
+    time.timeZone = "UTC";
+    services.httpd = {
+      enable = true;
+      adminAddr = "please@dont.contact";
+      phpPackage = php;
+      enablePHP = true;
+      phpOptions = "pcre.jit = true";
+      extraConfig =
+        let
+          testRoot = pkgs.writeText "index.php"
+            ''
+              <?php
+              preg_match('/(${testString})/', '${testString}', $result);
+              var_dump($result);
+            '';
+        in
+        ''
+          Alias / ${testRoot}/
+
+          <Directory ${testRoot}>
+            Require all granted
+          </Directory>
+        '';
+    };
+  };
+  testScript = let
+    # PCRE JIT SEAlloc feature does not play well with fork()
+    # The feature needs to either be disabled or PHP configured correctly
+    # More information in https://bugs.php.net/bug.php?id=78927 and https://bugs.php.net/bug.php?id=78630
+    pcreJitSeallocForkIssue = pkgs.writeText "pcre-jit-sealloc-issue.php" ''
+      <?php
+      preg_match('/nixos/', 'nixos');
+      $pid = pcntl_fork();
+      pcntl_wait($pid);
+    '';
+  in ''
+      machine.wait_for_unit("httpd.service")
+      # Ensure php evaluation by matching on the var_dump syntax
+      response = machine.succeed("curl -fvvv -s http://127.0.0.1:80/index.php")
+      expected = 'string(${toString (builtins.stringLength testString)}) "${testString}"'
+      assert expected in response, "Does not appear to be able to use subgroups."
+      machine.succeed("${php}/bin/php -f ${pcreJitSeallocForkIssue}")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/pict-rs.nix b/nixpkgs/nixos/tests/pict-rs.nix
new file mode 100644
index 000000000000..4315e9fb6e90
--- /dev/null
+++ b/nixpkgs/nixos/tests/pict-rs.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  {
+    name = "pict-rs";
+    meta.maintainers = with lib.maintainers; [ happysalada ];
+
+    nodes.machine = { ... }: {
+      environment.systemPackages = with pkgs; [ curl jq ];
+      services.pict-rs.enable = true;
+    };
+
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("pict-rs")
+      machine.wait_for_open_port(8080)
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/pinnwand.nix b/nixpkgs/nixos/tests/pinnwand.nix
new file mode 100644
index 000000000000..42b26e08c189
--- /dev/null
+++ b/nixpkgs/nixos/tests/pinnwand.nix
@@ -0,0 +1,93 @@
+import ./make-test-python.nix ({ pkgs, ...}:
+let
+  port = 8000;
+  baseUrl = "http://server:${toString port}";
+in
+{
+  name = "pinnwand";
+  meta = with pkgs.lib.maintainers; {
+    maintainers =[ hexa ];
+  };
+
+  nodes = {
+    server = { config, ... }:
+    {
+      networking.firewall.allowedTCPPorts = [
+        port
+      ];
+
+      services.pinnwand = {
+        enable = true;
+        port = port;
+      };
+    };
+
+    client = { pkgs, ... }:
+    {
+      environment.systemPackages = [
+        pkgs.steck
+
+        (pkgs.writers.writePython3Bin "setup-steck.py" {
+          libraries = with pkgs.python3.pkgs; [ appdirs toml ];
+          flakeIgnore = [
+            "E501"
+          ];
+        }
+        ''
+          import appdirs
+          import toml
+          import os
+
+          CONFIG = {
+              "base": "${baseUrl}/",
+              "confirm": False,
+              "magic": True,
+              "ignore": True
+          }
+
+          os.makedirs(appdirs.user_config_dir('steck'))
+          with open(os.path.join(appdirs.user_config_dir('steck'), 'steck.toml'), "w") as fd:
+              toml.dump(CONFIG, fd)
+        '')
+      ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("pinnwand.service")
+    client.wait_for_unit("network.target")
+
+    # create steck.toml config file
+    client.succeed("setup-steck.py")
+
+    # wait until the server running pinnwand is reachable
+    client.wait_until_succeeds("ping -c1 server")
+
+    # make sure pinnwand is listening
+    server.wait_for_open_port(${toString port})
+
+    # send the contents of /etc/machine-id
+    response = client.succeed("steck paste /etc/machine-id")
+
+    # parse the steck response
+    raw_url = None
+    removal_link = None
+    for line in response.split("\n"):
+        if line.startswith("View link:"):
+            raw_url = f"${baseUrl}/raw/{line.split('/')[-1]}"
+        if line.startswith("Removal link:"):
+            removal_link = line.split(":", 1)[1]
+
+    # check whether paste matches what we sent
+    client.succeed(f"curl {raw_url} > /tmp/machine-id")
+    client.succeed("diff /tmp/machine-id /etc/machine-id")
+
+    # remove paste and check that it's not available any more
+    client.succeed(f"curl {removal_link}")
+    client.fail(f"curl --fail {raw_url}")
+
+    server.log(server.execute("systemd-analyze security pinnwand | grep '✗'")[1])
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plantuml-server.nix b/nixpkgs/nixos/tests/plantuml-server.nix
new file mode 100644
index 000000000000..460c30919aec
--- /dev/null
+++ b/nixpkgs/nixos/tests/plantuml-server.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "plantuml-server";
+  meta.maintainers = with lib.maintainers; [ anthonyroussel ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.plantuml-server.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("plantuml-server.service")
+    machine.wait_for_open_port(8080)
+
+    with subtest("Generate chart"):
+      chart_id = machine.succeed("curl -sSf http://localhost:8080/plantuml/coder -d 'Alice -> Bob'")
+      machine.succeed("curl -sSf http://localhost:8080/plantuml/txt/{}".format(chart_id))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plasma-bigscreen.nix b/nixpkgs/nixos/tests/plasma-bigscreen.nix
new file mode 100644
index 000000000000..2fe90fa9b539
--- /dev/null
+++ b/nixpkgs/nixos/tests/plasma-bigscreen.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "plasma-bigscreen";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ttuegel k900 ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.sddm.enable = true;
+    services.xserver.displayManager.defaultSession = "plasma-bigscreen-x11";
+    services.xserver.desktopManager.plasma5.bigscreen.enable = true;
+    services.xserver.displayManager.autoLogin = {
+      enable = true;
+      user = "alice";
+    };
+
+    users.users.alice.extraGroups = ["uinput"];
+  };
+
+  testScript = { nodes, ... }: ''
+    with subtest("Wait for login"):
+        start_all()
+        machine.wait_for_file("/tmp/xauth_*")
+        machine.succeed("xauth merge /tmp/xauth_*")
+
+    with subtest("Check plasmashell started"):
+        machine.wait_until_succeeds("pgrep plasmashell")
+        machine.wait_for_window("Plasma Big Screen")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plasma5-systemd-start.nix b/nixpkgs/nixos/tests/plasma5-systemd-start.nix
new file mode 100644
index 000000000000..31a313af308b
--- /dev/null
+++ b/nixpkgs/nixos/tests/plasma5-systemd-start.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "plasma5-systemd-start";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ oxalica ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+    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";
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    with subtest("Wait for login"):
+        start_all()
+        machine.wait_for_file("/tmp/xauth_*")
+        machine.succeed("xauth merge /tmp/xauth_*")
+
+    with subtest("Check plasmashell started"):
+        machine.wait_until_succeeds("pgrep plasmashell")
+        machine.wait_for_window("^Desktop ")
+
+    status, result = machine.systemctl('--no-pager show plasma-plasmashell.service', user='alice')
+    assert status == 0, 'Service not found'
+    assert 'ActiveState=active' in result.split('\n'), 'Systemd service not active'
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plasma5.nix b/nixpkgs/nixos/tests/plasma5.nix
new file mode 100644
index 000000000000..fb8a5b73832e
--- /dev/null
+++ b/nixpkgs/nixos/tests/plasma5.nix
@@ -0,0 +1,67 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "plasma5";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ttuegel ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.displayManager.sddm.enable = true;
+    services.xserver.displayManager.defaultSession = "plasma";
+    services.xserver.desktopManager.plasma5.enable = true;
+    environment.plasma5.excludePackages = [ pkgs.plasma5Packages.elisa ];
+    services.xserver.displayManager.autoLogin = {
+      enable = true;
+      user = "alice";
+    };
+    hardware.pulseaudio.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    xdo = "${pkgs.xdotool}/bin/xdotool";
+  in ''
+    with subtest("Wait for login"):
+        start_all()
+        machine.wait_for_file("/tmp/xauth_*")
+        machine.succeed("xauth merge /tmp/xauth_*")
+
+    with subtest("Check plasmashell started"):
+        machine.wait_until_succeeds("pgrep plasmashell")
+        machine.wait_for_window("^Desktop ")
+
+    with subtest("Check that KDED is running"):
+        machine.succeed("pgrep kded5")
+
+    with subtest("Check that logging in has given the user ownership of devices"):
+        machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+    with subtest("Ensure Elisa is not installed"):
+        machine.fail("which elisa")
+
+    machine.succeed("su - ${user.name} -c 'xauth merge /tmp/xauth_*'")
+
+    with subtest("Run Dolphin"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 dolphin >&2 &'")
+        machine.wait_for_window(" Dolphin")
+
+    with subtest("Run Konsole"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 konsole >&2 &'")
+        machine.wait_for_window("Konsole")
+
+    with subtest("Run systemsettings"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0.0 systemsettings5 >&2 &'")
+        machine.wait_for_window("Settings")
+
+    with subtest("Wait to get a screenshot"):
+        machine.execute(
+            "${xdo} key Alt+F1 sleep 10"
+        )
+        machine.screenshot("screen")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plausible.nix b/nixpkgs/nixos/tests/plausible.nix
new file mode 100644
index 000000000000..9c26c509a5ab
--- /dev/null
+++ b/nixpkgs/nixos/tests/plausible.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "plausible";
+  meta = with lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.memorySize = 4096;
+    services.plausible = {
+      enable = true;
+      adminUser = {
+        email = "admin@example.org";
+        passwordFile = "${pkgs.writeText "pwd" "foobar"}";
+        activate = true;
+      };
+      server = {
+        baseUrl = "http://localhost:8000";
+        secretKeybaseFile = "${pkgs.writeText "dont-try-this-at-home" "nannannannannannannannannannannannannannannannannannannan_batman!"}";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("plausible.service")
+    machine.wait_for_open_port(8000)
+
+    # Ensure that the software does not make not make the machine
+    # listen on any public interfaces by default.
+    machine.fail("ss -tlpn 'src = 0.0.0.0 or src = [::]' | grep LISTEN")
+
+    machine.succeed("curl -f localhost:8000 >&2")
+
+    machine.succeed("curl -f localhost:8000/js/script.js >&2")
+
+    csrf_token = machine.succeed(
+        "curl -c /tmp/cookies localhost:8000/login | grep '_csrf_token' | sed -E 's,.*value=\"(.*)\".*,\\1,g'"
+    )
+
+    machine.succeed(
+        f"curl -b /tmp/cookies -f -X POST localhost:8000/login -F email=admin@example.org -F password=foobar -F _csrf_token={csrf_token.strip()} -D headers"
+    )
+
+    # By ensuring that the user is redirected to the dashboard after login, we
+    # also make sure that the automatic verification of the module works.
+    machine.succeed(
+        "[[ $(grep 'location: ' headers | cut -d: -f2- | xargs echo) == /sites* ]]"
+    )
+
+    machine.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/please.nix b/nixpkgs/nixos/tests/please.nix
new file mode 100644
index 000000000000..af825ae4b9b3
--- /dev/null
+++ b/nixpkgs/nixos/tests/please.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "please";
+  meta.maintainers = with lib.maintainers; [ azahi ];
+
+  nodes.machine =
+    { ... }:
+    {
+      users.users = lib.mkMerge [
+        (lib.listToAttrs (map
+          (n: lib.nameValuePair n { isNormalUser = true; })
+          (lib.genList (x: "user${toString x}") 6)))
+        {
+          user0.extraGroups = [ "wheel" ];
+        }
+      ];
+
+      security.please = {
+        enable = true;
+        wheelNeedsPassword = false;
+        settings = {
+          user2_run_true_as_root = {
+            name = "user2";
+            target = "root";
+            rule = "/run/current-system/sw/bin/true";
+            require_pass = false;
+          };
+          user4_edit_etc_hosts_as_root = {
+            name = "user4";
+            type = "edit";
+            target = "root";
+            rule = "/etc/hosts";
+            editmode = 644;
+            require_pass = false;
+          };
+        };
+      };
+    };
+
+  testScript = ''
+    with subtest("root: can run anything by default"):
+        machine.succeed('please true')
+    with subtest("root: can edit anything by default"):
+        machine.succeed('EDITOR=cat pleaseedit /etc/hosts')
+
+    with subtest("user0: can run as root because it's in the wheel group"):
+        machine.succeed('su - user0 -c "please -u root true"')
+    with subtest("user1: cannot run as root because it's not in the wheel group"):
+        machine.fail('su - user1 -c "please -u root true"')
+
+    with subtest("user0: can edit as root"):
+        machine.succeed('su - user0 -c "EDITOR=cat pleaseedit /etc/hosts"')
+    with subtest("user1: cannot edit as root"):
+        machine.fail('su - user1 -c "EDITOR=cat pleaseedit /etc/hosts"')
+
+    with subtest("user2: can run 'true' as root"):
+        machine.succeed('su - user2 -c "please -u root true"')
+    with subtest("user3: cannot run 'true' as root"):
+        machine.fail('su - user3 -c "please -u root true"')
+
+    with subtest("user4: can edit /etc/hosts"):
+        machine.succeed('su - user4 -c "EDITOR=cat pleaseedit /etc/hosts"')
+    with subtest("user5: cannot edit /etc/hosts"):
+        machine.fail('su - user5 -c "EDITOR=cat pleaseedit /etc/hosts"')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pleroma.nix b/nixpkgs/nixos/tests/pleroma.nix
new file mode 100644
index 000000000000..08a01585f877
--- /dev/null
+++ b/nixpkgs/nixos/tests/pleroma.nix
@@ -0,0 +1,254 @@
+/*
+  Pleroma E2E VM test.
+
+  Abstract:
+  =========
+  Using pleroma, postgresql, a local CA cert, a nginx reverse proxy
+  and a toot-based client, we're going to:
+
+  1. Provision a pleroma service from scratch (pleroma config + postgres db).
+  2. Create a "jamy" admin user.
+  3. Send a toot from this user.
+  4. Send a upload from this user.
+  5. Check the toot is part of the server public timeline
+
+  Notes:
+  - We need a fully functional TLS setup without having any access to
+    the internet. We do that by issuing a self-signed cert, add this
+    self-cert to the hosts pki trust store and finally spoof the
+    hostnames using /etc/hosts.
+  - For this NixOS test, we *had* to store some DB-related and
+    pleroma-related secrets to the store. Keep in mind the store is
+    world-readable, it's the worst place possible to store *any*
+    secret. **DO NOT DO THIS IN A REAL WORLD DEPLOYMENT**.
+*/
+
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+  send-toot = pkgs.writeScriptBin "send-toot" ''
+    set -eux
+    # toot is using the requests library internally. This library
+    # sadly embed its own certificate store instead of relying on the
+    # system one. Overriding this pretty bad default behaviour.
+    export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
+
+    echo "jamy-password" | toot login_cli -i "pleroma.nixos.test" -e "jamy@nixos.test"
+    echo "Login OK"
+
+    # Send a toot then verify it's part of the public timeline
+    echo "y" | toot post "hello world Jamy here"
+    echo "Send toot OK"
+    echo "y" | toot timeline | grep -c "hello world Jamy here"
+    echo "Get toot from timeline OK"
+
+    # Test file upload
+    echo "y" | toot upload ${db-seed} | grep -c "https://pleroma.nixos.test/media"
+    echo "File upload OK"
+
+    echo "====================================================="
+    echo "=                   SUCCESS                         ="
+    echo "=                                                   ="
+    echo "=    We were able to sent a toot + a upload and     ="
+    echo "=   retrieve both of them in the public timeline.   ="
+    echo "====================================================="
+  '';
+
+  provision-db = pkgs.writeScriptBin "provision-db" ''
+    set -eux
+    sudo -u postgres psql -f ${db-seed}
+  '';
+
+  test-db-passwd = "SccZOvTGM//BMrpoQj68JJkjDkMGb4pHv2cECWiI+XhVe3uGJTLI0vFV/gDlZ5jJ";
+
+  /* For this NixOS test, we *had* to store this secret to the store.
+    Keep in mind the store is world-readable, it's the worst place
+    possible to store *any* secret. **DO NOT DO THIS IN A REAL WORLD
+    DEPLOYMENT**.*/
+  db-seed = pkgs.writeText "provision.psql" ''
+    CREATE USER pleroma WITH ENCRYPTED PASSWORD '${test-db-passwd}';
+    CREATE DATABASE pleroma OWNER pleroma;
+    \c pleroma;
+    --Extensions made by ecto.migrate that need superuser access
+    CREATE EXTENSION IF NOT EXISTS citext;
+    CREATE EXTENSION IF NOT EXISTS pg_trgm;
+    CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+  '';
+
+  pleroma-conf = ''
+    import Config
+
+    config :pleroma, Pleroma.Web.Endpoint,
+       url: [host: "pleroma.nixos.test", scheme: "https", port: 443],
+       http: [ip: {127, 0, 0, 1}, port: 4000]
+
+    config :pleroma, :instance,
+      name: "NixOS test pleroma server",
+      email: "pleroma@nixos.test",
+      notify_email: "pleroma@nixos.test",
+      limit: 5000,
+      registrations_open: true
+
+    config :pleroma, :media_proxy,
+      enabled: false,
+      redirect_on_failure: true
+      #base_url: "https://cache.pleroma.social"
+
+    config :pleroma, Pleroma.Repo,
+      adapter: Ecto.Adapters.Postgres,
+      username: "pleroma",
+      password: "${test-db-passwd}",
+      database: "pleroma",
+      hostname: "localhost",
+      pool_size: 10,
+      prepare: :named,
+      parameters: [
+        plan_cache_mode: "force_custom_plan"
+      ]
+
+    config :pleroma, :database, rum_enabled: false
+    config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
+    config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
+    config :pleroma, configurable_from_database: false
+  '';
+
+  /* For this NixOS test, we *had* to store this secret to the store.
+    Keep in mind the store is world-readable, it's the worst place
+    possible to store *any* secret. **DO NOT DO THIS IN A REAL WORLD
+    DEPLOYMENT**.
+    In a real-word deployment, you'd handle this either by:
+    - manually upload your pleroma secrets to /var/lib/pleroma/secrets.exs
+    - use a deployment tool such as morph or NixOps to deploy your secrets.
+  */
+  pleroma-conf-secret = pkgs.writeText "secrets.exs" ''
+    import Config
+
+    config :joken, default_signer: "PS69/wMW7X6FIQPABt9lwvlZvgrJIncfiAMrK9J5mjVus/7/NJJi1DsDA1OghBE5"
+
+    config :pleroma, Pleroma.Web.Endpoint,
+       secret_key_base: "NvfmU7lYaQrmmxt4NACm0AaAfN9t6WxsrX0NCB4awkGHvr1S7jyshlEmrjaPFhhq",
+       signing_salt: "3L41+BuJ"
+
+    config :web_push_encryption, :vapid_details,
+      subject: "mailto:pleroma@nixos.test",
+      public_key: "BKjfNX9-UqAcncaNqERQtF7n9pKrB0-MO-juv6U5E5XQr_Tg5D-f8AlRjduAguDpyAngeDzG8MdrTejMSL4VF30",
+      private_key: "k7o9onKMQrgMjMb6l4fsxSaXO0BTNAer5MVSje3q60k"
+  '';
+
+  /* For this NixOS test, we *had* to store this secret to the store.
+    Keep in mind the store is world-readable, it's the worst place
+    possible to store *any* secret. **DO NOT DO THIS IN A REAL WORLD
+    DEPLOYMENT**.
+    In a real-word deployment, you'd handle this either by:
+    - manually upload your pleroma secrets to /var/lib/pleroma/secrets.exs
+    - use a deployment tool such as morph or NixOps to deploy your secrets.
+    */
+  provision-secrets = pkgs.writeScriptBin "provision-secrets" ''
+    set -eux
+    cp "${pleroma-conf-secret}" "/var/lib/pleroma/secrets.exs"
+    chown pleroma:pleroma /var/lib/pleroma/secrets.exs
+  '';
+
+  /* For this NixOS test, we *had* to store this secret to the store.
+    Keep in mind the store is world-readable, it's the worst place
+    possible to store *any* secret. **DO NOT DO THIS IN A REAL WORLD
+    DEPLOYMENT**.
+  */
+  provision-user = pkgs.writeScriptBin "provision-user" ''
+    set -eux
+
+    # Waiting for pleroma to be up.
+    timeout 5m bash -c 'while [[ "$(curl -s -o /dev/null -w '%{http_code}' https://pleroma.nixos.test/api/v1/instance)" != "200" ]]; do sleep 2; done'
+    # Toremove the RELEASE_COOKIE bit when https://github.com/NixOS/nixpkgs/issues/166229 gets fixed.
+    RELEASE_COOKIE="/var/lib/pleroma/.cookie" \
+      pleroma_ctl user new jamy jamy@nixos.test --password 'jamy-password' --moderator --admin -y
+  '';
+
+  tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    mkdir -p $out
+    openssl req -x509 \
+      -subj '/CN=pleroma.nixos.test/' -days 49710 \
+      -addext 'subjectAltName = DNS:pleroma.nixos.test' \
+      -keyout "$out/key.pem" -newkey ed25519 \
+      -out "$out/cert.pem" -noenc
+  '';
+
+  hosts = nodes: ''
+    ${nodes.pleroma.networking.primaryIPAddress} pleroma.nixos.test
+    ${nodes.client.networking.primaryIPAddress} client.nixos.test
+  '';
+  in {
+  name = "pleroma";
+  nodes = {
+    client = { nodes, pkgs, config, ... }: {
+      security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+      networking.extraHosts = hosts nodes;
+      environment.systemPackages = with pkgs; [
+        pkgs.toot
+        send-toot
+      ];
+    };
+    pleroma = { nodes, pkgs, config, ... }: {
+      security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+      networking.extraHosts = hosts nodes;
+      networking.firewall.enable = false;
+      environment.systemPackages = with pkgs; [
+        provision-db
+        provision-secrets
+        provision-user
+      ];
+      services = {
+        pleroma = {
+          enable = true;
+          configs = [
+            pleroma-conf
+          ];
+        };
+        postgresql = {
+          enable = true;
+          package = pkgs.postgresql_12;
+        };
+        nginx = {
+          enable = true;
+          virtualHosts."pleroma.nixos.test" = {
+            addSSL = true;
+            sslCertificate = "${tls-cert}/cert.pem";
+            sslCertificateKey = "${tls-cert}/key.pem";
+            locations."/" = {
+              proxyPass = "http://127.0.0.1:4000";
+              extraConfig = ''
+                add_header 'Access-Control-Allow-Origin' '*' always;
+                add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always;
+                add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always;
+                add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always;
+                if ($request_method = OPTIONS) {
+                    return 204;
+                }
+                add_header X-XSS-Protection "1; mode=block";
+                add_header X-Permitted-Cross-Domain-Policies none;
+                add_header X-Frame-Options DENY;
+                add_header X-Content-Type-Options nosniff;
+                add_header Referrer-Policy same-origin;
+                add_header X-Download-Options noopen;
+                proxy_http_version 1.1;
+                proxy_set_header Upgrade $http_upgrade;
+                proxy_set_header Connection "upgrade";
+                proxy_set_header Host $host;
+                client_max_body_size 16m;
+              '';
+            };
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    pleroma.wait_for_unit("postgresql.service")
+    pleroma.succeed("provision-db")
+    pleroma.succeed("provision-secrets")
+    pleroma.systemctl("restart pleroma.service")
+    pleroma.wait_for_unit("pleroma.service")
+    pleroma.succeed("provision-user")
+    client.succeed("send-toot")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plikd.nix b/nixpkgs/nixos/tests/plikd.nix
new file mode 100644
index 000000000000..97c254a5f7b0
--- /dev/null
+++ b/nixpkgs/nixos/tests/plikd.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "plikd";
+  meta = with lib.maintainers; {
+    maintainers = [ freezeboy ];
+  };
+
+  nodes.machine = { pkgs, ... }: let
+  in {
+    services.plikd.enable = true;
+    environment.systemPackages = [ pkgs.plik ];
+  };
+
+  testScript = ''
+    # Service basic test
+    machine.wait_for_unit("plikd")
+
+    # Network test
+    machine.wait_for_open_port(8080)
+    machine.succeed("curl --fail -v http://localhost:8080")
+
+    # Application test
+    machine.execute("echo test > /tmp/data.txt")
+    machine.succeed("plik --server http://localhost:8080 /tmp/data.txt | grep curl")
+
+    machine.succeed("diff data.txt /tmp/data.txt")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/plotinus.nix b/nixpkgs/nixos/tests/plotinus.nix
new file mode 100644
index 000000000000..b6ebab9b0198
--- /dev/null
+++ b/nixpkgs/nixos/tests/plotinus.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "plotinus";
+  meta = {
+    maintainers = pkgs.plotinus.meta.maintainers;
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      programs.plotinus.enable = true;
+      environment.systemPackages = [ pkgs.gnome.gnome-calculator pkgs.xdotool ];
+    };
+
+  testScript = ''
+    machine.wait_for_x()
+    machine.succeed("gnome-calculator >&2 &")
+    machine.wait_for_window("gnome-calculator")
+    machine.succeed(
+        "xdotool search --sync --onlyvisible --class gnome-calculator "
+        + "windowfocus --sync key --clearmodifiers --delay 1 'ctrl+shift+p'"
+    )
+    machine.sleep(5)  # wait for the popup
+    machine.succeed("xdotool key --delay 100 p r e f e r e n c e s Return")
+    machine.wait_for_window("Preferences")
+    machine.screenshot("screen")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/podgrab.nix b/nixpkgs/nixos/tests/podgrab.nix
new file mode 100644
index 000000000000..dc9dfebaf49b
--- /dev/null
+++ b/nixpkgs/nixos/tests/podgrab.nix
@@ -0,0 +1,34 @@
+let
+  defaultPort = 8080;
+  customPort = 4242;
+in
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "podgrab";
+
+  nodes = {
+    default = { ... }: {
+      services.podgrab.enable = true;
+    };
+
+    customized = { ... }: {
+      services.podgrab = {
+        enable = true;
+        port = customPort;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    default.wait_for_unit("podgrab")
+    default.wait_for_open_port(${toString defaultPort})
+    default.succeed("curl --fail http://localhost:${toString defaultPort}")
+
+    customized.wait_for_unit("podgrab")
+    customized.wait_for_open_port(${toString customPort})
+    customized.succeed("curl --fail http://localhost:${toString customPort}")
+  '';
+
+  meta.maintainers = with pkgs.lib.maintainers; [ ambroisie ];
+})
diff --git a/nixpkgs/nixos/tests/podman/default.nix b/nixpkgs/nixos/tests/podman/default.nix
new file mode 100644
index 000000000000..3eea45832f0a
--- /dev/null
+++ b/nixpkgs/nixos/tests/podman/default.nix
@@ -0,0 +1,181 @@
+import ../make-test-python.nix (
+  { pkgs, lib, ... }: {
+    name = "podman";
+    meta = {
+      maintainers = lib.teams.podman.members;
+    };
+
+    nodes = {
+      rootful = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        # hack to ensure that podman built with and without zfs in extraPackages is cached
+        boot.supportedFilesystems = [ "zfs" ];
+        networking.hostId = "00000000";
+      };
+      rootless = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        users.users.alice = {
+          isNormalUser = true;
+        };
+      };
+      dns = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
+      };
+      docker = { pkgs, ... }: {
+        virtualisation.podman.enable = true;
+
+        virtualisation.podman.dockerSocket.enable = true;
+
+        environment.systemPackages = [
+          pkgs.docker-client
+        ];
+
+        users.users.alice = {
+          isNormalUser = true;
+          extraGroups = [ "podman" ];
+        };
+
+        users.users.mallory = {
+          isNormalUser = true;
+        };
+      };
+    };
+
+    testScript = ''
+      import shlex
+
+
+      def su_cmd(cmd, user = "alice"):
+          cmd = shlex.quote(cmd)
+          return f"su {user} -l -c {cmd}"
+
+
+      rootful.wait_for_unit("sockets.target")
+      rootless.wait_for_unit("sockets.target")
+      dns.wait_for_unit("sockets.target")
+      docker.wait_for_unit("sockets.target")
+      start_all()
+
+      with subtest("Run container as root with runc"):
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
+              "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+          )
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
+
+      with subtest("Run container as root with crun"):
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
+              "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+          )
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
+
+      with subtest("Run container as root with the default backend"):
+          rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          rootful.succeed(
+              "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+          )
+          rootful.succeed("podman ps | grep sleeping")
+          rootful.succeed("podman stop sleeping")
+          rootful.succeed("podman rm sleeping")
+
+      # start systemd session for rootless
+      rootless.succeed("loginctl enable-linger alice")
+      rootless.succeed(su_cmd("whoami"))
+      rootless.sleep(1)
+
+      with subtest("Run container rootless with runc"):
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
+              su_cmd(
+                  "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+              )
+          )
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
+
+      with subtest("Run container rootless with crun"):
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
+              su_cmd(
+                  "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+              )
+          )
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
+
+      with subtest("Run container rootless with the default backend"):
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
+              su_cmd(
+                  "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
+              )
+          )
+          rootless.succeed(su_cmd("podman ps | grep sleeping"))
+          rootless.succeed(su_cmd("podman stop sleeping"))
+          rootless.succeed(su_cmd("podman rm sleeping"))
+
+      with subtest("rootlessport"):
+          rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg"))
+          rootless.succeed(
+              su_cmd(
+                  "podman run -d -p 9000:8888 --name=rootlessport -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Testing</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8888"
+              )
+          )
+          rootless.succeed(su_cmd("podman ps | grep rootlessport"))
+          rootless.wait_until_succeeds(su_cmd("${pkgs.curl}/bin/curl localhost:9000 | grep Testing"))
+          rootless.succeed(su_cmd("podman stop rootlessport"))
+          rootless.succeed(su_cmd("podman rm rootlessport"))
+
+      with subtest("Run container with init"):
+          rootful.succeed(
+              "tar cv -C ${pkgs.pkgsStatic.busybox} . | podman import - busybox"
+          )
+          pid = rootful.succeed("podman run --rm busybox readlink /proc/self").strip()
+          assert pid == "1"
+          pid = rootful.succeed("podman run --rm --init busybox readlink /proc/self").strip()
+          assert pid == "2"
+
+      with subtest("aardvark-dns"):
+          dns.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          dns.succeed(
+              "podman run -d --name=webserver -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Testing</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8000"
+          )
+          dns.succeed("podman ps | grep webserver")
+          dns.wait_until_succeeds(
+              "podman run --rm --name=client -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg ${pkgs.curl}/bin/curl http://webserver:8000 | grep Testing"
+          )
+          dns.succeed("podman stop webserver")
+          dns.succeed("podman rm webserver")
+
+      with subtest("A podman member can use the docker cli"):
+          docker.succeed(su_cmd("docker version"))
+
+      with subtest("Run container via docker cli"):
+          docker.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+          docker.succeed(
+            "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin localhost/scratchimg /bin/sleep 10"
+          )
+          docker.succeed("docker ps | grep sleeping")
+          docker.succeed("podman ps | grep sleeping")
+          docker.succeed("docker stop sleeping")
+          docker.succeed("docker rm sleeping")
+
+      with subtest("A podman non-member can not use the docker cli"):
+          docker.fail(su_cmd("docker version", user="mallory"))
+
+      # TODO: add docker-compose test
+
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix b/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix
new file mode 100644
index 000000000000..52c31dc21f10
--- /dev/null
+++ b/nixpkgs/nixos/tests/podman/tls-ghostunnel.nix
@@ -0,0 +1,147 @@
+/*
+  This test runs podman as a backend for the Docker CLI.
+ */
+import ../make-test-python.nix (
+  { pkgs, lib, ... }:
+
+  let gen-ca = pkgs.writeScript "gen-ca" ''
+    # Create CA
+    PATH="${pkgs.openssl}/bin:$PATH"
+    openssl genrsa -out ca-key.pem 4096
+    openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -subj '/C=NL/ST=Zuid-Holland/L=The Hague/O=Stevige Balken en Planken B.V./OU=OpSec/CN=Certificate Authority' -out ca.pem
+
+    # Create service
+    openssl genrsa -out podman-key.pem 4096
+    openssl req -subj '/CN=podman' -sha256 -new -key podman-key.pem -out service.csr
+    echo subjectAltName = DNS:podman,IP:127.0.0.1 >> extfile.cnf
+    echo extendedKeyUsage = serverAuth >> extfile.cnf
+    openssl x509 -req -days 365 -sha256 -in service.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out podman-cert.pem -extfile extfile.cnf
+
+    # Create client
+    openssl genrsa -out client-key.pem 4096
+    openssl req -subj '/CN=client' -new -key client-key.pem -out client.csr
+    echo extendedKeyUsage = clientAuth > extfile-client.cnf
+    openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile extfile-client.cnf
+
+    # Create CA 2
+    PATH="${pkgs.openssl}/bin:$PATH"
+    openssl genrsa -out ca-2-key.pem 4096
+    openssl req -new -x509 -days 365 -key ca-2-key.pem -sha256 -subj '/C=NL/ST=Zuid-Holland/L=The Hague/O=Stevige Balken en Planken B.V./OU=OpSec/CN=Certificate Authority' -out ca-2.pem
+
+    # Create client signed by CA 2
+    openssl genrsa -out client-2-key.pem 4096
+    openssl req -subj '/CN=client' -new -key client-2-key.pem -out client-2.csr
+    echo extendedKeyUsage = clientAuth > extfile-client.cnf
+    openssl x509 -req -days 365 -sha256 -in client-2.csr -CA ca-2.pem -CAkey ca-2-key.pem -CAcreateserial -out client-2-cert.pem -extfile extfile-client.cnf
+
+    '';
+  in
+  {
+    name = "podman-tls-ghostunnel";
+    meta = {
+      maintainers = lib.teams.podman.members ++ [ lib.maintainers.roberth ];
+    };
+
+    nodes = {
+      podman =
+        { pkgs, ... }:
+        {
+          virtualisation.podman.enable = true;
+          virtualisation.podman.dockerSocket.enable = true;
+          virtualisation.podman.networkSocket = {
+            enable = true;
+            openFirewall = true;
+            server = "ghostunnel";
+            tls.cert = "/root/podman-cert.pem";
+            tls.key = "/root/podman-key.pem";
+            tls.cacert = "/root/ca.pem";
+          };
+
+          environment.systemPackages = [
+            pkgs.docker-client
+          ];
+
+          users.users.alice = {
+            isNormalUser = true;
+            home = "/home/alice";
+            description = "Alice Foobar";
+            extraGroups = ["podman"];
+          };
+
+        };
+
+      client = { ... }: {
+        environment.systemPackages = [
+          # Installs the docker _client_ only
+          # Normally, you'd want `virtualisation.docker.enable = true;`.
+          pkgs.docker-client
+        ];
+        environment.variables.DOCKER_HOST = "podman:2376";
+        environment.variables.DOCKER_TLS_VERIFY = "1";
+      };
+    };
+
+    testScript = ''
+      import shlex
+
+
+      def su_cmd(user, cmd):
+          cmd = shlex.quote(cmd)
+          return f"su {user} -l -c {cmd}"
+
+      def cmd(command):
+        print(f"+{command}")
+        r = os.system(command)
+        if r != 0:
+          raise Exception(f"Command {command} failed with exit code {r}")
+
+      start_all()
+      cmd("${gen-ca}")
+
+      podman.copy_from_host("ca.pem", "/root/ca.pem")
+      podman.copy_from_host("podman-cert.pem", "/root/podman-cert.pem")
+      podman.copy_from_host("podman-key.pem", "/root/podman-key.pem")
+
+      client.copy_from_host("ca.pem", "/root/.docker/ca.pem")
+      # client.copy_from_host("podman-cert.pem", "/root/podman-cert.pem")
+      client.copy_from_host("client-cert.pem", "/root/.docker/cert.pem")
+      client.copy_from_host("client-key.pem", "/root/.docker/key.pem")
+
+      # TODO (ghostunnel): add file watchers so the restart isn't necessary
+      podman.succeed("systemctl reset-failed && systemctl restart ghostunnel-server-podman-socket.service")
+
+      podman.wait_for_unit("sockets.target")
+      podman.wait_for_unit("ghostunnel-server-podman-socket.service")
+
+      with subtest("Root docker cli also works"):
+          podman.succeed("docker version")
+
+      with subtest("A podman member can also still use the docker cli"):
+          podman.succeed(su_cmd("alice", "docker version"))
+
+      with subtest("Run container remotely via docker cli"):
+          client.succeed("docker version")
+
+          # via socket would be nicer
+          podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg")
+
+          client.succeed(
+            "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin localhost/scratchimg /bin/sleep 10"
+          )
+          client.succeed("docker ps | grep sleeping")
+          podman.succeed("docker ps | grep sleeping")
+          client.succeed("docker stop sleeping")
+          client.succeed("docker rm sleeping")
+
+      with subtest("Clients without cert will be denied"):
+          client.succeed("rm /root/.docker/{cert,key}.pem")
+          client.fail("docker version")
+
+      with subtest("Clients with wrong cert will be denied"):
+          client.copy_from_host("client-2-cert.pem", "/root/.docker/cert.pem")
+          client.copy_from_host("client-2-key.pem", "/root/.docker/key.pem")
+          client.fail("docker version")
+
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/polaris.nix b/nixpkgs/nixos/tests/polaris.nix
new file mode 100644
index 000000000000..bb105d600032
--- /dev/null
+++ b/nixpkgs/nixos/tests/polaris.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "polaris";
+  meta.maintainers = with lib.maintainers; [ pbsds ];
+
+  nodes.machine =
+    { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      services.polaris = {
+        enable = true;
+        port = 5050;
+        settings.users = [
+          {
+            name = "test_user";
+            password = "very_secret_password";
+            admin = true;
+          }
+        ];
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("polaris.service")
+    machine.wait_for_open_port(5050)
+    machine.succeed("curl http://localhost:5050/api/version")
+    machine.succeed("curl -X GET http://localhost:5050/api/initial_setup -H  'accept: application/json' | jq -e '.has_any_users == true'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pomerium.nix b/nixpkgs/nixos/tests/pomerium.nix
new file mode 100644
index 000000000000..d0204488e8ef
--- /dev/null
+++ b/nixpkgs/nixos/tests/pomerium.nix
@@ -0,0 +1,109 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "pomerium";
+  meta = with lib.maintainers; {
+    maintainers = [ lukegb devusb ];
+  };
+
+  nodes = let base = myIP: { pkgs, lib, ... }: {
+    virtualisation.vlans = [ 1 ];
+    networking = {
+      dhcpcd.enable = false;
+      firewall.allowedTCPPorts = [ 80 443 ];
+      hosts = {
+        "192.168.1.1" = [ "pomerium" "pom-auth" ];
+        "192.168.1.2" = [ "backend" "dummy-oidc" ];
+      };
+      interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+        { address = myIP; prefixLength = 24; }
+      ];
+    };
+  }; in {
+    pomerium = { pkgs, lib, ... }: {
+      imports = [ (base "192.168.1.1") ];
+      environment.systemPackages = with pkgs; [ chromium ];
+      services.pomerium = {
+        enable = true;
+        settings = {
+          address = ":80";
+          insecure_server = true;
+          authenticate_service_url = "http://pom-auth";
+
+          idp_provider = "oidc";
+          idp_scopes = [ "oidc" ];
+          idp_client_id = "dummy";
+          idp_provider_url = "http://dummy-oidc";
+
+          policy = [{
+            from = "https://my.website";
+            to = "http://192.168.1.2";
+            allow_public_unauthenticated_access = true;
+            preserve_host_header = true;
+          } {
+            from = "https://login.required";
+            to = "http://192.168.1.2";
+            allowed_domains = [ "my.domain" ];
+            preserve_host_header = true;
+          }];
+        };
+        secretsFile = pkgs.writeText "pomerium-secrets" ''
+          # 12345678901234567890123456789012 in base64
+          COOKIE_SECRET=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=
+          IDP_CLIENT_SECRET=dummy
+        '';
+      };
+    };
+    backend = { pkgs, lib, ... }: {
+      imports = [ (base "192.168.1.2") ];
+      services.nginx.enable = true;
+      services.nginx.virtualHosts."my.website" = {
+        root = pkgs.runCommand "testdir" {} ''
+          mkdir "$out"
+          echo hello world > "$out/index.html"
+        '';
+      };
+      services.nginx.virtualHosts."dummy-oidc" = {
+        root = pkgs.runCommand "testdir" {} ''
+          mkdir -p "$out/.well-known"
+          cat <<EOF >"$out/.well-known/openid-configuration"
+            {
+              "issuer": "http://dummy-oidc",
+              "authorization_endpoint": "http://dummy-oidc/auth.txt",
+              "token_endpoint": "http://dummy-oidc/token",
+              "jwks_uri": "http://dummy-oidc/jwks.json",
+              "userinfo_endpoint": "http://dummy-oidc/userinfo",
+              "id_token_signing_alg_values_supported": ["RS256"]
+            }
+          EOF
+          echo hello I am login page >"$out/auth.txt"
+        '';
+      };
+    };
+  };
+
+  testScript = { ... }: ''
+    backend.wait_for_unit("nginx")
+    backend.wait_for_open_port(80)
+
+    pomerium.wait_for_unit("pomerium")
+    pomerium.wait_for_open_port(80)
+
+    with subtest("no authentication required"):
+        pomerium.succeed(
+            "curl --resolve my.website:80:127.0.0.1 http://my.website | grep 'hello world'"
+        )
+
+    with subtest("login required"):
+        pomerium.succeed(
+            "curl -I --resolve login.required:80:127.0.0.1 http://login.required | grep pom-auth"
+        )
+        pomerium.succeed(
+            "curl -L --resolve login.required:80:127.0.0.1 http://login.required | grep 'hello I am login page'"
+        )
+
+    with subtest("ui"):
+        pomerium.succeed(
+          # check for a string that only appears if the UI is displayed correctly
+            "chromium --no-sandbox --headless --disable-gpu --dump-dom --host-resolver-rules='MAP login.required 127.0.0.1:80' http://login.required/.pomerium | grep 'User Details Not Available'"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/portunus.nix b/nixpkgs/nixos/tests/portunus.nix
new file mode 100644
index 000000000000..6fcae7e1c4ce
--- /dev/null
+++ b/nixpkgs/nixos/tests/portunus.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "portunus";
+  meta.maintainers = with lib.maintainers; [ SuperSandro2000 ];
+
+  nodes.machine = _: {
+    services.portunus = {
+      enable = true;
+      ldap.suffix = "dc=example,dc=org";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("portunus.service")
+    machine.succeed("curl --fail -vvv http://localhost:8080/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/postfix-raise-smtpd-tls-security-level.nix b/nixpkgs/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
new file mode 100644
index 000000000000..2a6c85a3a920
--- /dev/null
+++ b/nixpkgs/nixos/tests/postfix-raise-smtpd-tls-security-level.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix {
+  name = "postfix";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix = {
+      enable = true;
+      enableSubmissions = true;
+      submissionsOptions = {
+        smtpd_tls_security_level = "none";
+      };
+    };
+
+    environment.systemPackages = let
+      checkConfig = pkgs.writeScriptBin "check-config" ''
+        #!${pkgs.python3.interpreter}
+        import sys
+
+        state = 1
+        success = False
+
+        with open("/etc/postfix/master.cf") as masterCf:
+          for line in masterCf:
+            if state == 1 and line.startswith("submissions"):
+              state = 2
+            elif state == 2 and line.startswith(" ") and "smtpd_tls_security_level=encrypt" in line:
+              success = True
+            elif state == 2 and not line.startswith(" "):
+              state == 3
+        if not success:
+          sys.exit(1)
+      '';
+
+    in [ checkConfig ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("check-config")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/postfix.nix b/nixpkgs/nixos/tests/postfix.nix
new file mode 100644
index 000000000000..1dbe6a4c5193
--- /dev/null
+++ b/nixpkgs/nixos/tests/postfix.nix
@@ -0,0 +1,77 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in
+import ./make-test-python.nix {
+  name = "postfix";
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ common/user-account.nix ];
+    services.postfix = {
+      enable = true;
+      enableSubmission = true;
+      enableSubmissions = true;
+      tlsTrustedAuthorities = "${certs.ca.cert}";
+      sslCert = "${certs.${domain}.cert}";
+      sslKey = "${certs.${domain}.key}";
+      submissionsOptions = {
+          smtpd_sasl_auth_enable = "yes";
+          smtpd_client_restrictions = "permit";
+          milter_macro_daemon_name = "ORIGINATING";
+      };
+    };
+
+    security.pki.certificateFiles = [
+      certs.ca.cert
+    ];
+
+    networking.extraHosts = ''
+      127.0.0.1 ${domain}
+    '';
+
+    environment.systemPackages = let
+      sendTestMail = pkgs.writeScriptBin "send-testmail" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+
+        with smtplib.SMTP('${domain}') as smtp:
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test\n\nTest data.')
+          smtp.quit()
+      '';
+
+      sendTestMailStarttls = pkgs.writeScriptBin "send-testmail-starttls" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+        import ssl
+
+        ctx = ssl.create_default_context()
+
+        with smtplib.SMTP('${domain}') as smtp:
+          smtp.ehlo()
+          smtp.starttls(context=ctx)
+          smtp.ehlo()
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test STARTTLS\n\nTest data.')
+          smtp.quit()
+      '';
+
+      sendTestMailSmtps = pkgs.writeScriptBin "send-testmail-smtps" ''
+        #!${pkgs.python3.interpreter}
+        import smtplib
+        import ssl
+
+        ctx = ssl.create_default_context()
+
+        with smtplib.SMTP_SSL(host='${domain}', context=ctx) as smtp:
+          smtp.sendmail('root@localhost', 'alice@localhost', 'Subject: Test SMTPS\n\nTest data.')
+          smtp.quit()
+      '';
+    in [ sendTestMail sendTestMailStarttls sendTestMailSmtps ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("send-testmail")
+    machine.succeed("send-testmail-starttls")
+    machine.succeed("send-testmail-smtps")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/postfixadmin.nix b/nixpkgs/nixos/tests/postfixadmin.nix
new file mode 100644
index 000000000000..b2712f4699ae
--- /dev/null
+++ b/nixpkgs/nixos/tests/postfixadmin.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "postfixadmin";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ globin ];
+  };
+
+  nodes = {
+    postfixadmin = { config, pkgs, ... }: {
+      services.postfixadmin = {
+        enable = true;
+        hostName = "postfixadmin";
+        setupPasswordFile = pkgs.writeText "insecure-test-setup-pw-file" "$2y$10$r0p63YCjd9rb9nHrV9UtVuFgGTmPDLKu.0UIJoQTkWCZZze2iuB1m";
+      };
+      services.nginx.virtualHosts.postfixadmin = {
+        forceSSL = false;
+        enableACME = false;
+      };
+    };
+  };
+
+  testScript = ''
+    postfixadmin.start
+    postfixadmin.wait_for_unit("postgresql.service")
+    postfixadmin.wait_for_unit("phpfpm-postfixadmin.service")
+    postfixadmin.wait_for_unit("nginx.service")
+    postfixadmin.succeed(
+        "curl -sSfL http://postfixadmin/setup.php -X POST -F 'setup_password=not production'"
+    )
+    postfixadmin.succeed("curl -sSfL http://postfixadmin/ | grep 'Mail admins login here'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/postgis.nix b/nixpkgs/nixos/tests/postgis.nix
new file mode 100644
index 000000000000..dacf4e576c07
--- /dev/null
+++ b/nixpkgs/nixos/tests/postgis.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "postgis";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lsix ];
+  };
+
+  nodes = {
+    master =
+      { pkgs, ... }:
+
+      {
+        services.postgresql = {
+            enable = true;
+            package = pkgs.postgresql;
+            extraPlugins = ps: with ps; [
+              postgis
+            ];
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    master.wait_for_unit("postgresql")
+    master.sleep(10)  # Hopefully this is long enough!!
+    master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis;'")
+    master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_raster;'")
+    master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_topology;'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/postgresql-jit.nix b/nixpkgs/nixos/tests/postgresql-jit.nix
new file mode 100644
index 000000000000..baf26b8da2b3
--- /dev/null
+++ b/nixpkgs/nixos/tests/postgresql-jit.nix
@@ -0,0 +1,48 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (pkgs) lib;
+  packages = builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs);
+
+  mkJitTest = packageName: makeTest {
+    name = "${packageName}";
+    meta.maintainers = with lib.maintainers; [ ma27 ];
+    nodes.machine = { pkgs, lib, ... }: {
+      services.postgresql = {
+        enable = true;
+        enableJIT = true;
+        package = pkgs.${packageName};
+        initialScript = pkgs.writeText "init.sql" ''
+          create table demo (id int);
+          insert into demo (id) select generate_series(1, 5);
+        '';
+      };
+    };
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("postgresql.service")
+
+      with subtest("JIT is enabled"):
+          machine.succeed("sudo -u postgres psql <<<'show jit;' | grep 'on'")
+
+      with subtest("Test JIT works fine"):
+          output = machine.succeed(
+              "cat ${pkgs.writeText "test.sql" ''
+                set jit_above_cost = 1;
+                EXPLAIN ANALYZE SELECT CONCAT('jit result = ', SUM(id)) FROM demo;
+                SELECT CONCAT('jit result = ', SUM(id)) from demo;
+              ''} | sudo -u postgres psql"
+          )
+          assert "JIT:" in output
+          assert "jit result = 15" in output
+
+      machine.shutdown()
+    '';
+  };
+in
+lib.genAttrs packages mkJitTest
diff --git a/nixpkgs/nixos/tests/postgresql-wal-receiver.nix b/nixpkgs/nixos/tests/postgresql-wal-receiver.nix
new file mode 100644
index 000000000000..b0bd7711dbcd
--- /dev/null
+++ b/nixpkgs/nixos/tests/postgresql-wal-receiver.nix
@@ -0,0 +1,119 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  lib = pkgs.lib;
+
+  # Makes a test for a PostgreSQL package, given by name and looked up from `pkgs`.
+  makePostgresqlWalReceiverTest = postgresqlPackage:
+  {
+    name = postgresqlPackage;
+    value =
+      let
+        pkg = pkgs."${postgresqlPackage}";
+        postgresqlDataDir = "/var/lib/postgresql/${pkg.psqlSchema}";
+        replicationUser = "wal_receiver_user";
+        replicationSlot = "wal_receiver_slot";
+        replicationConn = "postgresql://${replicationUser}@localhost";
+        baseBackupDir = "/tmp/pg_basebackup";
+        walBackupDir = "/tmp/pg_wal";
+        atLeast12 = lib.versionAtLeast pkg.version "12.0";
+
+        recoveryFile = if atLeast12
+            then pkgs.writeTextDir "recovery.signal" ""
+            else pkgs.writeTextDir "recovery.conf" "restore_command = 'cp ${walBackupDir}/%f %p'";
+
+      in makeTest {
+        name = "postgresql-wal-receiver-${postgresqlPackage}";
+        meta.maintainers = with lib.maintainers; [ pacien ];
+
+        nodes.machine = { ... }: {
+          services.postgresql = {
+            package = pkg;
+            enable = true;
+            settings = lib.mkMerge [
+              {
+                wal_level = "archive"; # alias for replica on pg >= 9.6
+                max_wal_senders = 10;
+                max_replication_slots = 10;
+              }
+              (lib.mkIf atLeast12 {
+                restore_command = "cp ${walBackupDir}/%f %p";
+                recovery_end_command = "touch recovery.done";
+              })
+            ];
+            authentication = ''
+              host replication ${replicationUser} all trust
+            '';
+            initialScript = pkgs.writeText "init.sql" ''
+              create user ${replicationUser} replication;
+              select * from pg_create_physical_replication_slot('${replicationSlot}');
+            '';
+          };
+
+          services.postgresqlWalReceiver.receivers.main = {
+            postgresqlPackage = pkg;
+            connection = replicationConn;
+            slot = replicationSlot;
+            directory = walBackupDir;
+          };
+          # This is only to speedup test, it isn't time racing. Service is set to autorestart always,
+          # default 60sec is fine for real system, but is too much for a test
+          systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = lib.mkForce 5;
+        };
+
+        testScript = ''
+          # make an initial base backup
+          machine.wait_for_unit("postgresql")
+          machine.wait_for_unit("postgresql-wal-receiver-main")
+          # WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other
+          # required only for 9.4
+          machine.sleep(5)
+          machine.succeed(
+              "${pkg}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}"
+          )
+
+          # create a dummy table with 100 records
+          machine.succeed(
+              "sudo -u postgres psql --command='create table dummy as select * from generate_series(1, 100) as val;'"
+          )
+
+          # stop postgres and destroy data
+          machine.systemctl("stop postgresql")
+          machine.systemctl("stop postgresql-wal-receiver-main")
+          machine.succeed("rm -r ${postgresqlDataDir}/{base,global,pg_*}")
+
+          # restore the base backup
+          machine.succeed(
+              "cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}"
+          )
+
+          # prepare WAL and recovery
+          machine.succeed("chmod a+rX -R ${walBackupDir}")
+          machine.execute(
+              "for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done"
+          )  # make use of partial segments too
+          machine.succeed(
+              "cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*"
+          )
+
+          # replay WAL
+          machine.systemctl("start postgresql")
+          machine.wait_for_file("${postgresqlDataDir}/recovery.done")
+          machine.systemctl("restart postgresql")
+          machine.wait_for_unit("postgresql")
+
+          # check that our records have been restored
+          machine.succeed(
+              "test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100"
+          )
+        '';
+      };
+    };
+
+# Maps the generic function over all attributes of PostgreSQL packages
+in builtins.listToAttrs (map makePostgresqlWalReceiverTest (builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs)))
diff --git a/nixpkgs/nixos/tests/postgresql.nix b/nixpkgs/nixos/tests/postgresql.nix
new file mode 100644
index 000000000000..c0dd24cf6ad2
--- /dev/null
+++ b/nixpkgs/nixos/tests/postgresql.nix
@@ -0,0 +1,224 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE EXTENSION pgcrypto; -- just to check if lib loading works
+    CREATE TABLE sth (
+      id int
+    );
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    INSERT INTO sth (id) VALUES (1);
+    CREATE TABLE xmltest ( doc xml );
+    INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
+  '';
+  make-postgresql-test = postgresql-name: postgresql-package: backup-all: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ zagy ];
+    };
+
+    nodes.machine = {...}:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+        };
+
+        services.postgresqlBackup = {
+          enable = true;
+          databases = optional (!backup-all) "postgres";
+        };
+      };
+
+    testScript = let
+      backupName = if backup-all then "all" else "postgres";
+      backupService = if backup-all then "postgresqlBackup" else "postgresqlBackup-postgres";
+      backupFileBase = "/var/backup/postgresql/${backupName}";
+    in ''
+      def check_count(statement, lines):
+          return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format(
+              statement, lines
+          )
+
+
+      machine.start()
+      machine.wait_for_unit("postgresql")
+
+      with subtest("Postgresql is available just after unit start"):
+          machine.succeed(
+              "cat ${test-sql} | sudo -u postgres psql"
+          )
+
+      with subtest("Postgresql survives restart (bug #1735)"):
+          machine.shutdown()
+          import time
+          time.sleep(2)
+          machine.start()
+          machine.wait_for_unit("postgresql")
+
+      machine.fail(check_count("SELECT * FROM sth;", 3))
+      machine.succeed(check_count("SELECT * FROM sth;", 5))
+      machine.fail(check_count("SELECT * FROM sth;", 4))
+      machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1))
+
+      with subtest("Backup service works"):
+          machine.succeed(
+              "systemctl start ${backupService}.service",
+              "zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'",
+              "ls -hal /var/backup/postgresql/ >/dev/console",
+              "stat -c '%a' ${backupFileBase}.sql.gz | grep 600",
+          )
+      with subtest("Backup service removes prev files"):
+          machine.succeed(
+              # Create dummy prev files.
+              "touch ${backupFileBase}.prev.sql{,.gz,.zstd}",
+              "chown postgres:postgres ${backupFileBase}.prev.sql{,.gz,.zstd}",
+
+              # Run backup.
+              "systemctl start ${backupService}.service",
+              "ls -hal /var/backup/postgresql/ >/dev/console",
+
+              # Since nothing has changed in the database, the cur and prev files
+              # should match.
+              "zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'",
+              "cmp ${backupFileBase}.sql.gz ${backupFileBase}.prev.sql.gz",
+
+              # The prev files with unused suffix should be removed.
+              "[ ! -f '${backupFileBase}.prev.sql' ]",
+              "[ ! -f '${backupFileBase}.prev.sql.zstd' ]",
+
+              # Both cur and prev file should only be accessible by the postgres user.
+              "stat -c '%a' ${backupFileBase}.sql.gz | grep 600",
+              "stat -c '%a' '${backupFileBase}.prev.sql.gz' | grep 600",
+          )
+      with subtest("Backup service fails gracefully"):
+          # Sabotage the backup process
+          machine.succeed("rm /run/postgresql/.s.PGSQL.5432")
+          machine.fail(
+              "systemctl start ${backupService}.service",
+          )
+          machine.succeed(
+              "ls -hal /var/backup/postgresql/ >/dev/console",
+              "zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'",
+              "stat ${backupFileBase}.in-progress.sql.gz",
+          )
+          # In a previous version, the second run would overwrite prev.sql.gz,
+          # so we test a second run as well.
+          machine.fail(
+              "systemctl start ${backupService}.service",
+          )
+          machine.succeed(
+              "stat ${backupFileBase}.in-progress.sql.gz",
+              "zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'",
+          )
+
+
+      with subtest("Initdb works"):
+          machine.succeed("sudo -u postgres initdb -D /tmp/testpostgres2")
+
+      machine.shutdown()
+    '';
+
+  };
+
+  mk-ensure-clauses-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ zagy ];
+    };
+
+    nodes.machine = {...}:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          ensureUsers = [
+            {
+              name = "all-clauses";
+              ensureClauses = {
+                superuser = true;
+                createdb = true;
+                createrole = true;
+                "inherit" = true;
+                login = true;
+                replication = true;
+                bypassrls = true;
+              };
+            }
+            {
+              name = "default-clauses";
+            }
+          ];
+        };
+      };
+
+    testScript = let
+      getClausesQuery = user: pkgs.lib.concatStringsSep " "
+        [
+          "SELECT row_to_json(row)"
+          "FROM ("
+          "SELECT"
+            "rolsuper,"
+            "rolinherit,"
+            "rolcreaterole,"
+            "rolcreatedb,"
+            "rolcanlogin,"
+            "rolreplication,"
+            "rolbypassrls"
+          "FROM pg_roles"
+          "WHERE rolname = '${user}'"
+          ") row;"
+        ];
+    in ''
+      import json
+      machine.start()
+      machine.wait_for_unit("postgresql")
+
+      with subtest("All user permissions are set according to the ensureClauses attr"):
+          clauses = json.loads(
+            machine.succeed(
+                "sudo -u postgres psql -tc \"${getClausesQuery "all-clauses"}\""
+            )
+          )
+          print(clauses)
+          assert clauses['rolsuper'], 'expected user with clauses to have superuser clause'
+          assert clauses['rolinherit'], 'expected user with clauses to have inherit clause'
+          assert clauses['rolcreaterole'], 'expected user with clauses to have create role clause'
+          assert clauses['rolcreatedb'], 'expected user with clauses to have create db clause'
+          assert clauses['rolcanlogin'], 'expected user with clauses to have login clause'
+          assert clauses['rolreplication'], 'expected user with clauses to have replication clause'
+          assert clauses['rolbypassrls'], 'expected user with clauses to have bypassrls clause'
+
+      with subtest("All user permissions default when ensureClauses is not provided"):
+          clauses = json.loads(
+            machine.succeed(
+                "sudo -u postgres psql -tc \"${getClausesQuery "default-clauses"}\""
+            )
+          )
+          assert not clauses['rolsuper'], 'expected user with no clauses set to have default superuser clause'
+          assert clauses['rolinherit'], 'expected user with no clauses set to have default inherit clause'
+          assert not clauses['rolcreaterole'], 'expected user with no clauses set to have default create role clause'
+          assert not clauses['rolcreatedb'], 'expected user with no clauses set to have default create db clause'
+          assert clauses['rolcanlogin'], 'expected user with no clauses set to have default login clause'
+          assert not clauses['rolreplication'], 'expected user with no clauses set to have default replication clause'
+          assert not clauses['rolbypassrls'], 'expected user with no clauses set to have default bypassrls clause'
+
+      machine.shutdown()
+    '';
+  };
+in
+  concatMapAttrs (name: package: {
+    ${name} = make-postgresql-test name package false;
+    ${name + "-backup-all"} = make-postgresql-test "${name + "-backup-all"}" package true;
+    ${name + "-clauses"} = mk-ensure-clauses-test name package;
+  }) postgresql-versions
diff --git a/nixpkgs/nixos/tests/power-profiles-daemon.nix b/nixpkgs/nixos/tests/power-profiles-daemon.nix
new file mode 100644
index 000000000000..8a54d8e8bab8
--- /dev/null
+++ b/nixpkgs/nixos/tests/power-profiles-daemon.nix
@@ -0,0 +1,57 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "power-profiles-daemon";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mvnetbiz ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    security.polkit.enable = true;
+    services.power-profiles-daemon.enable = true;
+    environment.systemPackages = [ pkgs.glib pkgs.power-profiles-daemon ];
+  };
+
+  testScript = ''
+    def get_profile():
+        return machine.succeed(
+            """gdbus call --system --dest org.freedesktop.UPower.PowerProfiles --object-path /org/freedesktop/UPower/PowerProfiles \
+    --method org.freedesktop.DBus.Properties.Get 'org.freedesktop.UPower.PowerProfiles' 'ActiveProfile'
+    """
+        )
+
+
+    def set_profile(profile):
+        return machine.succeed(
+            """gdbus call --system --dest org.freedesktop.UPower.PowerProfiles --object-path /org/freedesktop/UPower/PowerProfiles \
+    --method org.freedesktop.DBus.Properties.Set 'org.freedesktop.UPower.PowerProfiles' 'ActiveProfile' "<'{profile}'>"
+    """.format(
+                profile=profile
+            )
+        )
+
+
+    machine.wait_for_unit("multi-user.target")
+
+    set_profile("power-saver")
+    profile = get_profile()
+    if not "power-saver" in profile:
+        raise Exception("Unable to set power-saver profile")
+
+
+    set_profile("balanced")
+    profile = get_profile()
+    if not "balanced" in profile:
+        raise Exception("Unable to set balanced profile")
+
+    # test powerprofilectl CLI
+    machine.succeed("powerprofilesctl set power-saver")
+    profile = get_profile()
+    if not "power-saver" in profile:
+        raise Exception("Unable to set power-saver profile with powerprofilectl")
+
+    machine.succeed("powerprofilesctl set balanced")
+    profile = get_profile()
+    if not "balanced" in profile:
+        raise Exception("Unable to set balanced profile with powerprofilectl")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/powerdns-admin.nix b/nixpkgs/nixos/tests/powerdns-admin.nix
new file mode 100644
index 000000000000..d326d74a9826
--- /dev/null
+++ b/nixpkgs/nixos/tests/powerdns-admin.nix
@@ -0,0 +1,139 @@
+# Test powerdns-admin
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+let
+  defaultConfig = ''
+    BIND_ADDRESS = '127.0.0.1'
+    PORT = 8000
+    CAPTCHA_ENABLE = False
+  '';
+
+  makeAppTest = name: configs: makeTest {
+    name = "powerdns-admin-${name}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ Flakebi zhaofengli ];
+    };
+
+    nodes.server = { pkgs, config, ... }: mkMerge ([
+      {
+        services.powerdns-admin = {
+          enable = true;
+          secretKeyFile = "/etc/powerdns-admin/secret";
+          saltFile = "/etc/powerdns-admin/salt";
+        };
+        # It's insecure to have secrets in the world-readable nix store, but this is just a test
+        environment.etc."powerdns-admin/secret".text = "secret key";
+        environment.etc."powerdns-admin/salt".text = "salt";
+        environment.systemPackages = [
+          (pkgs.writeShellScriptBin "run-test" config.system.build.testScript)
+        ];
+      }
+    ] ++ configs);
+
+    testScript = ''
+      server.wait_for_unit("powerdns-admin.service")
+      server.wait_until_succeeds("run-test", timeout=10)
+    '';
+  };
+
+  matrix = {
+    backend = {
+      mysql = {
+        services.powerdns-admin = {
+          config = ''
+            ${defaultConfig}
+            SQLALCHEMY_DATABASE_URI = 'mysql://powerdnsadmin@/powerdnsadmin?unix_socket=/run/mysqld/mysqld.sock'
+          '';
+        };
+        systemd.services.powerdns-admin = {
+          after = [ "mysql.service" ];
+          serviceConfig.BindPaths = "/run/mysqld";
+        };
+
+        services.mysql = {
+          enable = true;
+          package = pkgs.mariadb;
+          ensureDatabases = [ "powerdnsadmin" ];
+          ensureUsers = [
+            {
+              name = "powerdnsadmin";
+              ensurePermissions = {
+                "powerdnsadmin.*" = "ALL PRIVILEGES";
+              };
+            }
+          ];
+        };
+      };
+      postgresql = {
+        services.powerdns-admin = {
+          config = ''
+            ${defaultConfig}
+            SQLALCHEMY_DATABASE_URI = 'postgresql://powerdnsadmin@/powerdnsadmin?host=/run/postgresql'
+          '';
+        };
+        systemd.services.powerdns-admin = {
+          after = [ "postgresql.service" ];
+          serviceConfig.BindPaths = "/run/postgresql";
+        };
+
+        services.postgresql = {
+          enable = true;
+          ensureDatabases = [ "powerdnsadmin" ];
+          ensureUsers = [
+            {
+              name = "powerdnsadmin";
+              ensureDBOwnership = true;
+            }
+          ];
+        };
+      };
+    };
+    listen = {
+      tcp = {
+        services.powerdns-admin.extraArgs = [ "-b" "127.0.0.1:8000" ];
+        system.build.testScript = ''
+          set -euxo pipefail
+          curl -sSf http://127.0.0.1:8000/
+
+          # Create account to check that the database migrations ran
+          csrf_token="$(curl -sSfc session http://127.0.0.1:8000/register | grep _csrf_token | cut -d\" -f6)"
+          # Outputs 'Redirecting' if successful
+          curl -sSfb session http://127.0.0.1:8000/register \
+            -F "_csrf_token=$csrf_token" \
+            -F "firstname=first" \
+            -F "lastname=last" \
+            -F "email=a@example.com" \
+            -F "username=user" \
+            -F "password=password" \
+            -F "rpassword=password" | grep Redirecting
+
+          # Login
+          # Outputs 'Redirecting' if successful
+          curl -sSfb session http://127.0.0.1:8000/login \
+            -F "_csrf_token=$csrf_token" \
+            -F "username=user" \
+            -F "password=password" | grep Redirecting
+
+          # Check that we are logged in, this redirects to /admin/setting/pdns if we are
+          curl -sSfb session http://127.0.0.1:8000/dashboard/ | grep /admin/setting
+        '';
+      };
+      unix = {
+        services.powerdns-admin.extraArgs = [ "-b" "unix:/run/powerdns-admin/http.sock" ];
+        system.build.testScript = ''
+          curl -sSf --unix-socket /run/powerdns-admin/http.sock http://somehost/
+        '';
+      };
+    };
+  };
+in
+with matrix; {
+  postgresql = makeAppTest "postgresql" [ backend.postgresql listen.tcp ];
+  mysql = makeAppTest "mysql" [ backend.mysql listen.tcp ];
+  unix-listener = makeAppTest "unix-listener" [ backend.postgresql listen.unix ];
+}
diff --git a/nixpkgs/nixos/tests/powerdns.nix b/nixpkgs/nixos/tests/powerdns.nix
new file mode 100644
index 000000000000..599d5ea67efe
--- /dev/null
+++ b/nixpkgs/nixos/tests/powerdns.nix
@@ -0,0 +1,62 @@
+# This test runs PowerDNS authoritative server with the
+# generic MySQL backend (gmysql) to connect to a
+# MariaDB server using UNIX sockets authentication.
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "powerdns";
+
+  nodes.server = { ... }: {
+    services.powerdns.enable = true;
+    services.powerdns.extraConfig = ''
+      launch=gmysql
+      gmysql-user=pdns
+      zone-cache-refresh-interval=0
+    '';
+
+    services.mysql = {
+      enable = true;
+      package = pkgs.mariadb;
+      ensureDatabases = [ "powerdns" ];
+      ensureUsers = lib.singleton
+        { name = "pdns";
+          ensurePermissions = { "powerdns.*" = "ALL PRIVILEGES"; };
+        };
+    };
+
+    environment.systemPackages = with pkgs;
+      [ dnsutils powerdns mariadb ];
+  };
+
+  testScript = ''
+    with subtest("PowerDNS database exists"):
+        server.wait_for_unit("mysql")
+        server.succeed("echo 'SHOW DATABASES;' | sudo -u pdns mysql -u pdns >&2")
+
+    with subtest("Loading the MySQL schema works"):
+        server.succeed(
+            "sudo -u pdns mysql -u pdns -D powerdns <"
+            "${pkgs.powerdns}/share/doc/pdns/schema.mysql.sql"
+        )
+
+    with subtest("PowerDNS server starts"):
+        server.wait_for_unit("pdns")
+        server.succeed("dig version.bind txt chaos @127.0.0.1 >&2")
+
+    with subtest("Adding an example zone works"):
+        # Extract configuration file needed by pdnsutil
+        pdnsutil = "sudo -u pdns pdnsutil "
+        server.succeed(f"{pdnsutil} create-zone example.com ns1.example.com")
+        server.succeed(f"{pdnsutil} add-record  example.com ns1 A 192.168.1.2")
+
+    with subtest("Querying the example zone works"):
+        reply = server.succeed("dig +noall +answer ns1.example.com @127.0.0.1")
+        assert (
+            "192.168.1.2" in reply
+        ), f""""
+        The reply does not contain the expected IP address:
+          Expected:
+            ns1.example.com.        3600    IN      A       192.168.1.2
+          Reply:
+            {reply}"""
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pppd.nix b/nixpkgs/nixos/tests/pppd.nix
new file mode 100644
index 000000000000..d599f918036f
--- /dev/null
+++ b/nixpkgs/nixos/tests/pppd.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix (
+  let
+    chap-secrets = {
+      text = ''"flynn" * "reindeerflotilla" *'';
+      mode = "0640";
+    };
+  in {
+    name = "pppd";
+
+    nodes = {
+      server = {config, pkgs, ...}: {
+        config = {
+          # Run a PPPoE access concentrator server. It will spawn an
+          # appropriate PPP server process when a PPPoE client sets up a
+          # PPPoE session.
+          systemd.services.pppoe-server = {
+            restartTriggers = [
+              config.environment.etc."ppp/pppoe-server-options".source
+              config.environment.etc."ppp/chap-secrets".source
+            ];
+            after = ["network.target"];
+            serviceConfig = {
+              ExecStart = "${pkgs.rpPPPoE}/sbin/pppoe-server -F -O /etc/ppp/pppoe-server-options -q ${pkgs.ppp}/sbin/pppd -I eth1 -L 192.0.2.1 -R 192.0.2.2";
+            };
+            wantedBy = ["multi-user.target"];
+          };
+          environment.etc = {
+            "ppp/pppoe-server-options".text = ''
+              lcp-echo-interval 10
+              lcp-echo-failure 2
+              plugin pppoe.so
+              require-chap
+              nobsdcomp
+              noccp
+              novj
+            '';
+            "ppp/chap-secrets" = chap-secrets;
+          };
+        };
+      };
+      client = {config, pkgs, ...}: {
+        services.pppd = {
+          enable = true;
+          peers.test = {
+            config = ''
+              plugin pppoe.so eth1
+              name "flynn"
+              noipdefault
+              persist
+              noauth
+              debug
+            '';
+          };
+        };
+        environment.etc."ppp/chap-secrets" = chap-secrets;
+      };
+    };
+
+    testScript = ''
+      start_all()
+      client.wait_until_succeeds("ping -c1 -W1 192.0.2.1")
+      server.wait_until_succeeds("ping -c1 -W1 192.0.2.2")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/predictable-interface-names.nix b/nixpkgs/nixos/tests/predictable-interface-names.nix
new file mode 100644
index 000000000000..51d5e8ae59b9
--- /dev/null
+++ b/nixpkgs/nixos/tests/predictable-interface-names.nix
@@ -0,0 +1,60 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  testCombinations = pkgs.lib.cartesianProductOfSets {
+    predictable = [true false];
+    withNetworkd = [true false];
+    systemdStage1 = [true false];
+  };
+in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd, systemdStage1 }: {
+  name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
+       + pkgs.lib.optionalString withNetworkd "Networkd"
+       + pkgs.lib.optionalString systemdStage1 "SystemdStage1";
+  value = makeTest {
+    name = pkgs.lib.optionalString (!predictable) "un" + "predictableInterfaceNames"
+         + pkgs.lib.optionalString withNetworkd "-with-networkd"
+         + pkgs.lib.optionalString systemdStage1 "-systemd-stage-1";
+    meta = {};
+
+    nodes.machine = { lib, ... }: let
+      script = ''
+        ip link
+        if ${lib.optionalString predictable "!"} ip link show eth0; then
+          echo Success
+        else
+          exit 1
+        fi
+      '';
+    in {
+      networking.usePredictableInterfaceNames = lib.mkForce predictable;
+      networking.useNetworkd = withNetworkd;
+      networking.dhcpcd.enable = !withNetworkd;
+      networking.useDHCP = !withNetworkd;
+
+      # Check if predictable interface names are working in stage-1
+      boot.initrd.postDeviceCommands = lib.mkIf (!systemdStage1) script;
+
+      boot.initrd.systemd = lib.mkIf systemdStage1 {
+        enable = true;
+        initrdBin = [ pkgs.iproute2 ];
+        services.systemd-udev-settle.wantedBy = ["initrd.target"];
+        services.check-interfaces = {
+          requiredBy = ["initrd.target"];
+          after = ["systemd-udev-settle.service"];
+          serviceConfig.Type = "oneshot";
+          path = [ pkgs.iproute2 ];
+          inherit script;
+        };
+      };
+    };
+
+    testScript = ''
+      print(machine.succeed("ip link"))
+      machine.${if predictable then "fail" else "succeed"}("ip link show eth0")
+    '';
+  };
+}) testCombinations)
diff --git a/nixpkgs/nixos/tests/printing.nix b/nixpkgs/nixos/tests/printing.nix
new file mode 100644
index 000000000000..29c5d810f215
--- /dev/null
+++ b/nixpkgs/nixos/tests/printing.nix
@@ -0,0 +1,122 @@
+# Test printing via CUPS.
+
+import ./make-test-python.nix (
+{ pkgs
+, socket ? true # whether to use socket activation
+, ...
+}:
+
+{
+  name = "printing";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ domenkozar eelco matthewbauer ];
+  };
+
+  nodes.server = { ... }: {
+    services.printing = {
+      enable = true;
+      stateless = true;
+      startWhenNeeded = socket;
+      listenAddresses = [ "*:631" ];
+      defaultShared = true;
+      openFirewall = true;
+      extraConf = ''
+        <Location />
+          Order allow,deny
+          Allow from all
+        </Location>
+      '';
+    };
+    # Add a HP Deskjet printer connected via USB to the server.
+    hardware.printers.ensurePrinters = [{
+      name = "DeskjetLocal";
+      deviceUri = "usb://foobar/printers/foobar";
+      model = "drv:///sample.drv/deskjet.ppd";
+    }];
+  };
+
+  nodes.client = { ... }: {
+    services.printing.enable = true;
+    services.printing.startWhenNeeded = socket;
+    # Add printer to the client as well, via IPP.
+    hardware.printers.ensurePrinters = [{
+      name = "DeskjetRemote";
+      deviceUri = "ipp://server/printers/DeskjetLocal";
+      model = "drv:///sample.drv/deskjet.ppd";
+    }];
+    hardware.printers.ensureDefaultPrinter = "DeskjetRemote";
+  };
+
+  testScript = ''
+    import os
+    import re
+
+    start_all()
+
+    with subtest("Make sure that cups is up on both sides and printers are set up"):
+        server.wait_for_unit("cups.${if socket then "socket" else "service"}")
+        client.wait_for_unit("cups.${if socket then "socket" else "service"}")
+
+    assert "scheduler is running" in client.succeed("lpstat -r")
+
+    with subtest("UNIX socket is used for connections"):
+        assert "/var/run/cups/cups.sock" in client.succeed("lpstat -H")
+
+    with subtest("HTTP server is available too"):
+        client.succeed("curl --fail http://localhost:631/")
+        client.succeed(f"curl --fail http://{server.name}:631/")
+        server.fail(f"curl --fail --connect-timeout 2 http://{client.name}:631/")
+
+    with subtest("LP status checks"):
+        assert "DeskjetRemote accepting requests" in client.succeed("lpstat -a")
+        assert "DeskjetLocal accepting requests" in client.succeed(
+            f"lpstat -h {server.name}:631 -a"
+        )
+        client.succeed("cupsdisable DeskjetRemote")
+        out = client.succeed("lpq")
+        print(out)
+        assert re.search(
+            "DeskjetRemote is not ready.*no entries",
+            client.succeed("lpq"),
+            flags=re.DOTALL,
+        )
+        client.succeed("cupsenable DeskjetRemote")
+        assert re.match(
+            "DeskjetRemote is ready.*no entries", client.succeed("lpq"), flags=re.DOTALL
+        )
+
+    # Test printing various file types.
+    for file in [
+        "${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf",
+        "${pkgs.groff.doc}/share/doc/*/meref.ps",
+        "${pkgs.cups.out}/share/doc/cups/images/cups.png",
+        "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt",
+    ]:
+        file_name = os.path.basename(file)
+        with subtest(f"print {file_name}"):
+            # Print the file on the client.
+            print(client.succeed("lpq"))
+            client.succeed(f"lp {file}")
+            client.wait_until_succeeds(
+                f"lpq; lpq | grep -q -E 'active.*root.*{file_name}'"
+            )
+
+            # Ensure that a raw PCL file appeared in the server's queue
+            # (showing that the right filters have been applied).  Of
+            # course, since there is no actual USB printer attached, the
+            # file will stay in the queue forever.
+            server.wait_for_file("/var/spool/cups/d*-001")
+            server.wait_until_succeeds(f"lpq -a | grep -q -E '{file_name}'")
+
+            # Delete the job on the client.  It should disappear on the
+            # server as well.
+            client.succeed("lprm")
+            client.wait_until_succeeds("lpq -a | grep -q -E 'no entries'")
+
+            retry(lambda _: "no entries" in server.succeed("lpq -a"))
+
+            # The queue is empty already, so this should be safe.
+            # Otherwise, pairs of "c*"-"d*-001" files might persist.
+            server.execute("rm /var/spool/cups/*")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/privoxy.nix b/nixpkgs/nixos/tests/privoxy.nix
new file mode 100644
index 000000000000..2d95c4522a01
--- /dev/null
+++ b/nixpkgs/nixos/tests/privoxy.nix
@@ -0,0 +1,113 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  # Note: For some reason Privoxy can't issue valid
+  # certificates if the CA is generated using gnutls :(
+  certs = pkgs.runCommand "example-certs"
+    { buildInputs = [ pkgs.openssl ]; }
+    ''
+      mkdir $out
+
+      # generate CA keypair
+      openssl req -new -nodes -x509 \
+        -extensions v3_ca -keyout $out/ca.key \
+        -out $out/ca.crt -days 365 \
+        -subj "/O=Privoxy CA/CN=Privoxy CA"
+
+      # generate server key/signing request
+      openssl genrsa -out $out/server.key 3072
+      openssl req -new -key $out/server.key \
+        -out server.csr -sha256 \
+        -subj "/O=An unhappy server./CN=example.com"
+
+      # sign the request/generate the certificate
+      openssl x509 -req -in server.csr -CA $out/ca.crt \
+      -CAkey $out/ca.key -CAcreateserial -out $out/server.crt \
+      -days 500 -sha256
+    '';
+in
+
+{
+  name = "privoxy";
+  meta = with lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  nodes.machine = { ... }: {
+    services.nginx.enable = true;
+    services.nginx.virtualHosts."example.com" = {
+      addSSL = true;
+      sslCertificate = "${certs}/server.crt";
+      sslCertificateKey = "${certs}/server.key";
+      locations."/".root = pkgs.writeTextFile
+        { name = "bad-day";
+          destination = "/how-are-you/index.html";
+          text = "I've had a bad day!\n";
+        };
+      locations."/ads".extraConfig = ''
+        return 200 "Hot Nixpkgs PRs in your area. Click here!\n";
+      '';
+    };
+
+    services.privoxy = {
+      enable = true;
+      inspectHttps = true;
+      settings = {
+        ca-cert-file = "${certs}/ca.crt";
+        ca-key-file  = "${certs}/ca.key";
+        debug = 65536;
+      };
+      userActions = ''
+        {+filter{positive}}
+        example.com
+
+        {+block{Fake ads}}
+        example.com/ads
+      '';
+      userFilters = ''
+        FILTER: positive This is a filter example.
+        s/bad/great/ig
+      '';
+    };
+
+    security.pki.certificateFiles = [ "${certs}/ca.crt" ];
+
+    networking.hosts."::1" = [ "example.com" ];
+    networking.proxy.httpProxy = "http://localhost:8118";
+    networking.proxy.httpsProxy = "http://localhost:8118";
+  };
+
+  testScript =
+    ''
+      with subtest("Privoxy is running"):
+          machine.wait_for_unit("privoxy")
+          machine.wait_for_open_port(8118)
+          machine.succeed("curl -f http://config.privoxy.org")
+
+      with subtest("Privoxy can filter http requests"):
+          machine.wait_for_open_port(80)
+          assert "great day" in machine.succeed(
+              "curl -sfL http://example.com/how-are-you? | tee /dev/stderr"
+          )
+
+      with subtest("Privoxy can filter https requests"):
+          machine.wait_for_open_port(443)
+          assert "great day" in machine.succeed(
+              "curl -sfL https://example.com/how-are-you? | tee /dev/stderr"
+          )
+
+      with subtest("Blocks are working"):
+          machine.wait_for_open_port(443)
+          machine.fail("curl -f https://example.com/ads 1>&2")
+          machine.succeed("curl -f https://example.com/PRIVOXY-FORCE/ads 1>&2")
+
+      with subtest("Temporary certificates are cleaned"):
+          # Count current certificates
+          machine.succeed("test $(ls /run/privoxy/certs | wc -l) -gt 0")
+          # Forward in time 12 days, trigger the timer..
+          machine.succeed("date -s \"$(date --date '12 days')\"")
+          machine.systemctl("start systemd-tmpfiles-clean")
+          # ...and count again
+          machine.succeed("test $(ls /run/privoxy/certs | wc -l) -eq 0")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/prometheus-exporters.nix b/nixpkgs/nixos/tests/prometheus-exporters.nix
new file mode 100644
index 000000000000..632656ad5795
--- /dev/null
+++ b/nixpkgs/nixos/tests/prometheus-exporters.nix
@@ -0,0 +1,1740 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) concatStringsSep maintainers mapAttrs mkMerge
+    removeSuffix replaceStrings singleton splitString makeBinPath;
+
+  /*
+    * The attrset `exporterTests` contains one attribute
+    * for each exporter test. Each of these attributes
+    * is expected to be an attrset containing:
+    *
+    *  `exporterConfig`:
+    *    this attribute set contains config for the exporter itself
+    *
+    *  `exporterTest`
+    *    this attribute set contains test instructions
+    *
+    *  `metricProvider` (optional)
+    *    this attribute contains additional machine config
+    *
+    *  `nodeName` (optional)
+    *    override an incompatible testnode name
+    *
+    *  Example:
+    *    exporterTests.<exporterName> = {
+    *      exporterConfig = {
+    *        enable = true;
+    *      };
+    *      metricProvider = {
+    *        services.<metricProvider>.enable = true;
+    *      };
+    *      exporterTest = ''
+    *        wait_for_unit("prometheus-<exporterName>-exporter.service")
+    *        wait_for_open_port(1234)
+    *        succeed("curl -sSf 'localhost:1234/metrics'")
+    *      '';
+    *    };
+    *
+    *  # this would generate the following test config:
+    *
+    *    nodes.<exporterName> = {
+    *      services.prometheus.<exporterName> = {
+    *        enable = true;
+    *      };
+    *      services.<metricProvider>.enable = true;
+    *    };
+    *
+    *    testScript = ''
+    *      <exporterName>.start()
+    *      <exporterName>.wait_for_unit("prometheus-<exporterName>-exporter.service")
+    *      <exporterName>.wait_for_open_port(1234)
+    *      <exporterName>.succeed("curl -sSf 'localhost:1234/metrics'")
+    *      <exporterName>.shutdown()
+    *    '';
+  */
+
+  exporterTests = {
+    apcupsd = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.apcupsd.enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("apcupsd.service")
+        wait_for_open_port(3551)
+        wait_for_unit("prometheus-apcupsd-exporter.service")
+        wait_for_open_port(9162)
+        succeed("curl -sSf http://localhost:9162/metrics | grep 'apcupsd_info'")
+      '';
+    };
+
+    artifactory = {
+      exporterConfig = {
+        enable = true;
+        artiUsername = "artifactory-username";
+        artiPassword = "artifactory-password";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-artifactory-exporter.service")
+        wait_for_open_port(9531)
+        succeed(
+            "curl -sSf http://localhost:9531/metrics | grep 'artifactory_up'"
+        )
+      '';
+    };
+
+    bind = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.bind.enable = true;
+        services.bind.extraConfig = ''
+          statistics-channels {
+            inet 127.0.0.1 port 8053 allow { localhost; };
+          };
+        '';
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-bind-exporter.service")
+        wait_for_open_port(9119)
+        succeed(
+            "curl -sSf http://localhost:9119/metrics | grep 'bind_query_recursions_total 0'"
+        )
+      '';
+    };
+
+    bird = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.bird2.enable = true;
+        services.bird2.config = ''
+          router id 127.0.0.1;
+
+          protocol kernel MyObviousTestString {
+            ipv4 {
+              import all;
+              export none;
+            };
+          }
+
+          protocol device {
+          }
+        '';
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-bird-exporter.service")
+        wait_for_open_port(9324)
+        wait_until_succeeds(
+            "curl -sSf http://localhost:9324/metrics | grep 'MyObviousTestString'"
+        )
+      '';
+    };
+
+    bitcoin = {
+      exporterConfig = {
+        enable = true;
+        rpcUser = "bitcoinrpc";
+        rpcPasswordFile = pkgs.writeText "password" "hunter2";
+      };
+      metricProvider = {
+        services.bitcoind.default.enable = true;
+        services.bitcoind.default.rpc.users.bitcoinrpc.passwordHMAC = "e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-bitcoin-exporter.service")
+        wait_for_unit("bitcoind-default.service")
+        wait_for_open_port(9332)
+        succeed("curl -sSf http://localhost:9332/metrics | grep '^bitcoin_blocks '")
+      '';
+    };
+
+    blackbox = {
+      exporterConfig = {
+        enable = true;
+        configFile = pkgs.writeText "config.yml" (builtins.toJSON {
+          modules.icmp_v6 = {
+            prober = "icmp";
+            icmp.preferred_ip_protocol = "ip6";
+          };
+        });
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-blackbox-exporter.service")
+        wait_for_open_port(9115)
+        succeed(
+            "curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep 'probe_success 1'"
+        )
+      '';
+    };
+
+    collectd = {
+      exporterConfig = {
+        enable = true;
+        extraFlags = [ "--web.collectd-push-path /collectd" ];
+      };
+      exporterTest = let postData = replaceStrings [ "\n" ] [ "" ] ''
+        [{
+          "values":[23],
+          "dstypes":["gauge"],
+          "type":"gauge",
+          "interval":1000,
+          "host":"testhost",
+          "plugin":"testplugin",
+          "time":DATE
+        }]
+      ''; in
+        ''
+          wait_for_unit("prometheus-collectd-exporter.service")
+          wait_for_open_port(9103)
+          succeed(
+              'echo \'${postData}\'> /tmp/data.json'
+          )
+          succeed('sed -ie "s DATE $(date +%s) " /tmp/data.json')
+          succeed(
+              "curl -sSfH 'Content-Type: application/json' -X POST --data @/tmp/data.json localhost:9103/collectd"
+          )
+          succeed(
+              "curl -sSf localhost:9103/metrics | grep 'collectd_testplugin_gauge{instance=\"testhost\"} 23'"
+          )
+        '';
+    };
+
+    dnsmasq = {
+      exporterConfig = {
+        enable = true;
+        leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
+      };
+      metricProvider = {
+        services.dnsmasq.enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("dnsmasq.service")
+        wait_for_open_port(53)
+        wait_for_file("/var/lib/dnsmasq/dnsmasq.leases")
+        wait_for_unit("prometheus-dnsmasq-exporter.service")
+        wait_for_open_port(9153)
+        succeed("curl -sSf http://localhost:9153/metrics | grep 'dnsmasq_leases 0'")
+      '';
+    };
+
+    # Access to WHOIS server is required to properly test this exporter, so
+    # just perform basic sanity check that the exporter is running and returns
+    # a failure.
+    domain = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-domain-exporter.service")
+        wait_for_open_port(9222)
+        succeed("curl -sSf 'http://localhost:9222/probe?target=nixos.org'")
+      '';
+    };
+
+    dovecot = {
+      exporterConfig = {
+        enable = true;
+        scopes = [ "global" ];
+        socketPath = "/var/run/dovecot2/old-stats";
+        user = "root"; # <- don't use user root in production
+      };
+      metricProvider = {
+        services.dovecot2.enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-dovecot-exporter.service")
+        wait_for_open_port(9166)
+        succeed(
+            "curl -sSf http://localhost:9166/metrics | grep 'dovecot_up{scope=\"global\"} 1'"
+        )
+      '';
+    };
+
+    fastly = {
+      exporterConfig = {
+        enable = true;
+        tokenPath = pkgs.writeText "token" "abc123";
+      };
+
+      # noop: fastly's exporter can't start without first talking to fastly
+      # see: https://github.com/peterbourgon/fastly-exporter/issues/87
+      exporterTest = ''
+        succeed("true");
+      '';
+    };
+
+    fritzbox = {
+      # TODO add proper test case
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-fritzbox-exporter.service")
+        wait_for_open_port(9133)
+        succeed(
+            "curl -sSf http://localhost:9133/metrics | grep 'fritzbox_exporter_collect_errors 0'"
+        )
+      '';
+    };
+
+    graphite = {
+      exporterConfig = {
+        enable = true;
+        port = 9108;
+        graphitePort = 9109;
+        mappingSettings.mappings = [{
+          match = "test.*.*";
+          name = "testing";
+          labels = {
+            protocol = "$1";
+            author = "$2";
+          };
+        }];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-graphite-exporter.service")
+        wait_for_open_port(9108)
+        wait_for_open_port(9109)
+        succeed("echo test.tcp.foo-bar 1234 $(date +%s) | nc -w1 localhost 9109")
+        succeed("curl -sSf http://localhost:9108/metrics | grep 'testing{author=\"foo-bar\",protocol=\"tcp\"} 1234'")
+      '';
+    };
+
+    idrac = {
+      exporterConfig = {
+        enable = true;
+        port = 9348;
+        configuration = {
+          hosts = {
+            default = { username = "username"; password = "password"; };
+          };
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-idrac-exporter.service")
+        wait_for_open_port(9348)
+        wait_until_succeeds("curl localhost:9348")
+      '';
+    };
+
+    influxdb = {
+      exporterConfig = {
+        enable = true;
+        sampleExpiry = "3s";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-influxdb-exporter.service")
+        wait_for_open_port(9122)
+        succeed(
+          "curl -XPOST http://localhost:9122/write --data-binary 'influxdb_exporter,distro=nixos,added_in=21.09 value=1'"
+        )
+        succeed(
+          "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
+        )
+        execute("sleep 5")
+        fail(
+          "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
+        )
+      '';
+    };
+
+    ipmi = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-ipmi-exporter.service")
+        wait_for_open_port(9290)
+        succeed(
+          "curl -sSf http://localhost:9290/metrics | grep 'ipmi_scrape_duration_seconds'"
+        )
+      '';
+    };
+
+    jitsi = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        systemd.services.prometheus-jitsi-exporter.after = [ "jitsi-videobridge2.service" ];
+        services.jitsi-videobridge = {
+          enable = true;
+          colibriRestApi = true;
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("jitsi-videobridge2.service")
+        wait_for_open_port(8080)
+        wait_for_unit("prometheus-jitsi-exporter.service")
+        wait_for_open_port(9700)
+        wait_until_succeeds(
+            'journalctl -eu prometheus-jitsi-exporter.service -o cat | grep "key=participants"'
+        )
+        succeed("curl -sSf 'localhost:9700/metrics' | grep 'jitsi_participants 0'")
+      '';
+    };
+
+    json = {
+      exporterConfig = {
+        enable = true;
+        url = "http://localhost";
+        configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON {
+          modules = {
+            default = {
+              metrics = [
+                { name = "json_test_metric"; path = "{ .test }"; }
+              ];
+            };
+          };
+        });
+      };
+      metricProvider = {
+        systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost.locations."/".extraConfig = ''
+            return 200 "{\"test\":1}";
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("nginx.service")
+        wait_for_open_port(80)
+        wait_for_unit("prometheus-json-exporter.service")
+        wait_for_open_port(7979)
+        succeed(
+            "curl -sSf 'localhost:7979/probe?target=http://localhost' | grep 'json_test_metric 1'"
+        )
+      '';
+    };
+
+    kea = let
+      controlSocketPathV4 = "/run/kea/dhcp4.sock";
+      controlSocketPathV6 = "/run/kea/dhcp6.sock";
+    in
+    {
+      exporterConfig = {
+        enable = true;
+        controlSocketPaths = [
+          controlSocketPathV4
+          controlSocketPathV6
+        ];
+      };
+      metricProvider = {
+        services.kea = {
+          dhcp4 = {
+            enable = true;
+            settings = {
+              control-socket = {
+                socket-type = "unix";
+                socket-name = controlSocketPathV4;
+              };
+            };
+          };
+          dhcp6 = {
+            enable = true;
+            settings = {
+              control-socket = {
+                socket-type = "unix";
+                socket-name = controlSocketPathV6;
+              };
+            };
+          };
+        };
+      };
+
+      exporterTest = ''
+        wait_for_unit("kea-dhcp4-server.service")
+        wait_for_unit("kea-dhcp6-server.service")
+        wait_for_file("${controlSocketPathV4}")
+        wait_for_file("${controlSocketPathV6}")
+        wait_for_unit("prometheus-kea-exporter.service")
+        wait_for_open_port(9547)
+        succeed(
+            "curl --fail localhost:9547/metrics | grep 'packets_received_total'"
+        )
+      '';
+    };
+
+    knot = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.knot = {
+          enable = true;
+          extraArgs = [ "-v" ];
+          settingsFile = pkgs.writeText "knot.conf" ''
+            server:
+              listen: 127.0.0.1@53
+
+            template:
+              - id: default
+                global-module: mod-stats
+                dnssec-signing: off
+                zonefile-sync: -1
+                journal-db: /var/lib/knot/journal
+                kasp-db: /var/lib/knot/kasp
+                timer-db: /var/lib/knot/timer
+                zonefile-load: difference
+                storage: ${pkgs.buildEnv {
+                  name = "foo";
+                  paths = [
+                    (pkgs.writeTextDir "test.zone" ''
+                      @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
+                      @       NS      ns1
+                      @       NS      ns2
+                      ns1     A       192.168.0.1
+                    '')
+                  ];
+                }}
+
+            mod-stats:
+              - id: custom
+                edns-presence: on
+                query-type: on
+
+            zone:
+              - domain: test
+                file: test.zone
+                module: mod-stats/custom
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("knot.service")
+        wait_for_unit("prometheus-knot-exporter.service")
+        wait_for_open_port(9433)
+        succeed("curl -sSf 'localhost:9433' | grep '2\.019031301'")
+      '';
+    };
+
+    keylight = {
+      # A hardware device is required to properly test this exporter, so just
+      # perform a couple of basic sanity checks that the exporter is running
+      # and requires a target, but cannot reach a specified target.
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-keylight-exporter.service")
+        wait_for_open_port(9288)
+        succeed(
+            "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics | grep '400'"
+        )
+        succeed(
+            "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics?target=nosuchdevice | grep '500'"
+        )
+      '';
+    };
+
+    lnd = {
+      exporterConfig = {
+        enable = true;
+        lndTlsPath = "/var/lib/lnd/tls.cert";
+        lndMacaroonDir = "/var/lib/lnd";
+        extraFlags = [ "--lnd.network=regtest" ];
+      };
+      metricProvider = {
+        systemd.services.prometheus-lnd-exporter.serviceConfig.RestartSec = 15;
+        systemd.services.prometheus-lnd-exporter.after = [ "lnd.service" ];
+        services.bitcoind.regtest = {
+          enable = true;
+          extraConfig = ''
+            rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7
+            zmqpubrawblock=tcp://127.0.0.1:28332
+            zmqpubrawtx=tcp://127.0.0.1:28333
+          '';
+          extraCmdlineOptions = [ "-regtest" ];
+        };
+        systemd.services.lnd = {
+          serviceConfig.ExecStart = ''
+            ${pkgs.lnd}/bin/lnd \
+              --datadir=/var/lib/lnd \
+              --tlscertpath=/var/lib/lnd/tls.cert \
+              --tlskeypath=/var/lib/lnd/tls.key \
+              --logdir=/var/log/lnd \
+              --bitcoin.active \
+              --bitcoin.regtest \
+              --bitcoin.node=bitcoind \
+              --bitcoind.rpcuser=bitcoinrpc \
+              --bitcoind.rpcpass=hunter2 \
+              --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 \
+              --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 \
+              --readonlymacaroonpath=/var/lib/lnd/readonly.macaroon
+          '';
+          serviceConfig.StateDirectory = "lnd";
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+        };
+        # initialize wallet, creates macaroon needed by exporter
+        systemd.services.lnd.postStart = ''
+          ${pkgs.curl}/bin/curl \
+            --retry 20 \
+            --retry-delay 1 \
+            --retry-connrefused \
+            --cacert /var/lib/lnd/tls.cert \
+            -X GET \
+            https://localhost:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /tmp/seed
+          ${pkgs.curl}/bin/curl \
+            --retry 20 \
+            --retry-delay 1 \
+            --retry-connrefused \
+            --cacert /var/lib/lnd/tls.cert \
+            -X POST \
+            -d "{\"wallet_password\": \"asdfasdfasdf\", \"cipher_seed_mnemonic\": $(cat /tmp/seed | tr -d '\n')}" \
+            https://localhost:8080/v1/initwallet
+        '';
+      };
+      exporterTest = ''
+        wait_for_unit("lnd.service")
+        wait_for_open_port(10009)
+        wait_for_unit("prometheus-lnd-exporter.service")
+        wait_for_open_port(9092)
+        succeed("curl -sSf localhost:9092/metrics | grep '^lnd_peer_count'")
+      '';
+    };
+
+    mail = {
+      exporterConfig = {
+        enable = true;
+        configuration = {
+          monitoringInterval = "2s";
+          mailCheckTimeout = "10s";
+          servers = [{
+            name = "testserver";
+            server = "localhost";
+            port = 25;
+            from = "mail-exporter@localhost";
+            to = "mail-exporter@localhost";
+            detectionDir = "/var/spool/mail/mail-exporter/new";
+          }];
+        };
+      };
+      metricProvider = {
+        services.postfix.enable = true;
+        systemd.services.prometheus-mail-exporter = {
+          after = [ "postfix.service" ];
+          requires = [ "postfix.service" ];
+          serviceConfig = {
+            ExecStartPre = [
+              "${pkgs.writeShellScript "create-maildir" ''
+                mkdir -p -m 0700 mail-exporter/new
+              ''}"
+            ];
+            ProtectHome = true;
+            ReadOnlyPaths = "/";
+            ReadWritePaths = "/var/spool/mail";
+            WorkingDirectory = "/var/spool/mail";
+          };
+        };
+        users.users.mailexporter = {
+          isSystemUser = true;
+          group = "mailexporter";
+        };
+        users.groups.mailexporter = {};
+      };
+      exporterTest = ''
+        wait_for_unit("postfix.service")
+        wait_for_unit("prometheus-mail-exporter.service")
+        wait_for_open_port(9225)
+        wait_until_succeeds(
+            "curl -sSf http://localhost:9225/metrics | grep 'mail_deliver_success{configname=\"testserver\"} 1'"
+        )
+      '';
+    };
+
+    mikrotik = {
+      exporterConfig = {
+        enable = true;
+        extraFlags = [ "-timeout=1s" ];
+        configuration = {
+          devices = [
+            {
+              name = "router";
+              address = "192.168.42.48";
+              user = "prometheus";
+              password = "shh";
+            }
+          ];
+          features = {
+            bgp = true;
+            dhcp = true;
+            dhcpl = true;
+            dhcpv6 = true;
+            health = true;
+            routes = true;
+            poe = true;
+            pools = true;
+            optics = true;
+            w60g = true;
+            wlansta = true;
+            wlanif = true;
+            monitor = true;
+            ipsec = true;
+          };
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-mikrotik-exporter.service")
+        wait_for_open_port(9436)
+        succeed(
+            "curl -sSf http://localhost:9436/metrics | grep 'mikrotik_scrape_collector_success{device=\"router\"} 0'"
+        )
+      '';
+    };
+
+    modemmanager = {
+      exporterConfig = {
+        enable = true;
+        refreshRate = "10s";
+      };
+      metricProvider = {
+        # ModemManager is installed when NetworkManager is enabled. Ensure it is
+        # started and is wanted by NM and the exporter to start everything up
+        # in the right order.
+        networking.networkmanager.enable = true;
+        systemd.services.ModemManager = {
+          enable = true;
+          wantedBy = [ "NetworkManager.service" "prometheus-modemmanager-exporter.service" ];
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("ModemManager.service")
+        wait_for_unit("prometheus-modemmanager-exporter.service")
+        wait_for_open_port(9539)
+        succeed(
+            "curl -sSf http://localhost:9539/metrics | grep 'modemmanager_info'"
+        )
+      '';
+    };
+
+    mysqld = {
+      exporterConfig = {
+        enable = true;
+        runAsLocalSuperUser = true;
+        configFile = pkgs.writeText "test-prometheus-exporter-mysqld-config.my-cnf" ''
+          [client]
+          user = exporter
+          password = snakeoilpassword
+        '';
+      };
+      metricProvider = {
+        services.mysql = {
+          enable = true;
+          package = pkgs.mariadb;
+          initialScript = pkgs.writeText "mysql-init-script.sql" ''
+            CREATE USER 'exporter'@'localhost'
+            IDENTIFIED BY 'snakeoilpassword'
+            WITH MAX_USER_CONNECTIONS 3;
+            GRANT PROCESS, REPLICATION CLIENT, SLAVE MONITOR, SELECT ON *.* TO 'exporter'@'localhost';
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-mysqld-exporter.service")
+        wait_for_open_port(9104)
+        wait_for_unit("mysql.service")
+        succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 1'")
+        systemctl("stop mysql.service")
+        succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 0'")
+        systemctl("start mysql.service")
+        wait_for_unit("mysql.service")
+        succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 1'")
+      '';
+    };
+
+    nextcloud = {
+      exporterConfig = {
+        enable = true;
+        passwordFile = "/var/nextcloud-pwfile";
+        url = "http://localhost";
+      };
+      metricProvider = {
+        systemd.services.nc-pwfile =
+          let
+            passfile = (pkgs.writeText "pwfile" "snakeoilpw");
+          in
+          {
+            requiredBy = [ "prometheus-nextcloud-exporter.service" ];
+            before = [ "prometheus-nextcloud-exporter.service" ];
+            serviceConfig.ExecStart = ''
+              ${pkgs.coreutils}/bin/install -o nextcloud-exporter -m 0400 ${passfile} /var/nextcloud-pwfile
+            '';
+          };
+        services.nginx = {
+          enable = true;
+          virtualHosts."localhost" = {
+            basicAuth.nextcloud-exporter = "snakeoilpw";
+            locations."/" = {
+              root = "${pkgs.prometheus-nextcloud-exporter.src}/serverinfo/testdata";
+              tryFiles = "/negative-space.json =404";
+            };
+          };
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("nginx.service")
+        wait_for_unit("prometheus-nextcloud-exporter.service")
+        wait_for_open_port(9205)
+        succeed("curl -sSf http://localhost:9205/metrics | grep 'nextcloud_up 1'")
+      '';
+    };
+
+    nginx = {
+      exporterConfig = {
+        enable = true;
+        constLabels = [ "foo=bar" ];
+      };
+      metricProvider = {
+        services.nginx = {
+          enable = true;
+          statusPage = true;
+          virtualHosts."test".extraConfig = "return 204;";
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("nginx.service")
+        wait_for_unit("prometheus-nginx-exporter.service")
+        wait_for_open_port(9113)
+        succeed("curl -sSf http://localhost:9113/metrics | grep 'nginx_up{foo=\"bar\"} 1'")
+      '';
+    };
+
+    nginxlog = {
+      exporterConfig = {
+        enable = true;
+        group = "nginx";
+        settings = {
+          namespaces = [
+            {
+              name = "filelogger";
+              source = {
+                files = [ "/var/log/nginx/filelogger.access.log" ];
+              };
+            }
+            {
+              name = "syslogger";
+              source = {
+                syslog = {
+                  listen_address = "udp://127.0.0.1:10000";
+                  format = "rfc3164";
+                  tags = [ "nginx" ];
+                };
+              };
+            }
+          ];
+        };
+      };
+      metricProvider = {
+        services.nginx = {
+          enable = true;
+          httpConfig = ''
+            server {
+              listen 80;
+              server_name filelogger.local;
+              access_log /var/log/nginx/filelogger.access.log;
+            }
+            server {
+              listen 81;
+              server_name syslogger.local;
+              access_log syslog:server=127.0.0.1:10000,tag=nginx,severity=info;
+            }
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("nginx.service")
+        wait_for_unit("prometheus-nginxlog-exporter.service")
+        wait_for_open_port(9117)
+        wait_for_open_port(80)
+        wait_for_open_port(81)
+        succeed("curl http://localhost")
+        execute("sleep 1")
+        succeed(
+            "curl -sSf http://localhost:9117/metrics | grep 'filelogger_http_response_count_total' | grep 1"
+        )
+        succeed("curl http://localhost:81")
+        execute("sleep 1")
+        succeed(
+            "curl -sSf http://localhost:9117/metrics | grep 'syslogger_http_response_count_total' | grep 1"
+        )
+      '';
+    };
+
+    node = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-node-exporter.service")
+        wait_for_open_port(9100)
+        succeed(
+            "curl -sSf http://localhost:9100/metrics | grep 'node_exporter_build_info{.\\+} 1'"
+        )
+      '';
+    };
+
+    openldap = {
+      exporterConfig = {
+        enable = true;
+        ldapCredentialFile = "${pkgs.writeText "exporter.yml" ''
+          ldapUser: "cn=root,dc=example"
+          ldapPass: "notapassword"
+        ''}";
+      };
+      metricProvider = {
+        services.openldap = {
+          enable = true;
+          settings.children = {
+            "cn=schema".includes = [
+              "${pkgs.openldap}/etc/schema/core.ldif"
+              "${pkgs.openldap}/etc/schema/cosine.ldif"
+              "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+              "${pkgs.openldap}/etc/schema/nis.ldif"
+            ];
+            "olcDatabase={1}mdb" = {
+              attrs = {
+                objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+                olcDatabase = "{1}mdb";
+                olcDbDirectory = "/var/db/openldap";
+                olcSuffix = "dc=example";
+                olcRootDN = {
+                  # cn=root,dc=example
+                  base64 = "Y249cm9vdCxkYz1leGFtcGxl";
+                };
+                olcRootPW = {
+                  path = "${pkgs.writeText "rootpw" "notapassword"}";
+                };
+              };
+            };
+            "olcDatabase={2}monitor".attrs = {
+              objectClass = [ "olcDatabaseConfig" ];
+              olcDatabase = "{2}monitor";
+              olcAccess = [ "to dn.subtree=cn=monitor by users read" ];
+            };
+          };
+          declarativeContents."dc=example" = ''
+            dn: dc=example
+            objectClass: domain
+            dc: example
+
+            dn: ou=users,dc=example
+            objectClass: organizationalUnit
+            ou: users
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-openldap-exporter.service")
+        wait_for_open_port(389)
+        wait_for_open_port(9330)
+        wait_until_succeeds(
+            "curl -sSf http://localhost:9330/metrics | grep 'openldap_scrape{result=\"ok\"} 1'"
+        )
+      '';
+    };
+
+    pgbouncer = {
+      exporterConfig = {
+        enable = true;
+        connectionStringFile = pkgs.writeText "connection.conf" "postgres://admin:@localhost:6432/pgbouncer?sslmode=disable";
+      };
+
+      metricProvider = {
+        services.postgresql.enable = true;
+        services.pgbouncer = {
+          # https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration
+          ignoreStartupParameters = "extra_float_digits";
+          enable = true;
+          listenAddress = "*";
+          databases = { postgres = "host=/run/postgresql/ port=5432 auth_user=postgres dbname=postgres"; };
+          authType = "any";
+          maxClientConn = 99;
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("postgresql.service")
+        wait_for_unit("pgbouncer.service")
+        wait_for_unit("prometheus-pgbouncer-exporter.service")
+        wait_for_open_port(9127)
+        succeed("curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_up 1'")
+        succeed(
+            "curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_config_max_client_connections 99'"
+        )
+      '';
+    };
+
+    php-fpm = {
+      nodeName = "php_fpm";
+      exporterConfig = {
+        enable = true;
+        environmentFile = pkgs.writeTextFile {
+          name = "/tmp/prometheus-php-fpm-exporter.env";
+          text = ''
+            PHP_FPM_SCRAPE_URI="tcp://127.0.0.1:9000/status"
+          '';
+        };
+      };
+      metricProvider = {
+        users.users."php-fpm-exporter" = {
+          isSystemUser = true;
+          group  = "php-fpm-exporter";
+        };
+        users.groups."php-fpm-exporter" = {};
+        services.phpfpm.pools."php-fpm-exporter" = {
+          user = "php-fpm-exporter";
+          group = "php-fpm-exporter";
+          settings = {
+            "pm" = "dynamic";
+            "pm.max_children" = 32;
+            "pm.max_requests" = 500;
+            "pm.start_servers" = 2;
+            "pm.min_spare_servers" = 2;
+            "pm.max_spare_servers" = 5;
+            "pm.status_path" = "/status";
+            "listen" = "127.0.0.1:9000";
+            "listen.allowed_clients" = "127.0.0.1";
+          };
+          phpEnv."PATH" = makeBinPath [ pkgs.php ];
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("phpfpm-php-fpm-exporter.service")
+        wait_for_unit("prometheus-php-fpm-exporter.service")
+        succeed("curl -sSf http://localhost:9253/metrics | grep 'phpfpm_up{.*} 1'")
+      '';
+    };
+
+    ping = {
+      exporterConfig = {
+        enable = true;
+
+        settings = {
+          targets = [ {
+            "localhost" = {
+              alias = "local machine";
+              env = "prod";
+              type = "domain";
+            };
+          } {
+            "127.0.0.1" = {
+              alias = "local machine";
+              type = "v4";
+            };
+          } {
+            "::1" = {
+              alias = "local machine";
+              type = "v6";
+            };
+          } {
+            "google.com" = {};
+          } ];
+          dns = {};
+          ping = {
+            interval = "2s";
+            timeout = "3s";
+            history-size = 42;
+            payload-size = 56;
+          };
+          log = {
+            level = "warn";
+          };
+        };
+      };
+
+      exporterTest = ''
+        wait_for_unit("prometheus-ping-exporter.service")
+        wait_for_open_port(9427)
+        succeed("curl -sSf http://localhost:9427/metrics | grep 'ping_up{.*} 1'")
+      '';
+    };
+
+    postfix = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.postfix.enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-postfix-exporter.service")
+        wait_for_file("/var/lib/postfix/queue/public/showq")
+        wait_for_open_port(9154)
+        wait_until_succeeds(
+            "curl -sSf http://localhost:9154/metrics | grep 'postfix_up{path=\"/var/lib/postfix/queue/public/showq\"} 1'"
+        )
+        succeed(
+            "curl -sSf http://localhost:9154/metrics | grep 'postfix_smtpd_connects_total 0'"
+        )
+        succeed("curl -sSf http://localhost:9154/metrics | grep 'postfix_up{.*} 1'")
+      '';
+    };
+
+    postgres = {
+      exporterConfig = {
+        enable = true;
+        runAsLocalSuperUser = true;
+      };
+      metricProvider = {
+        services.postgresql.enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-postgres-exporter.service")
+        wait_for_open_port(9187)
+        wait_for_unit("postgresql.service")
+        succeed(
+            "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
+        )
+        succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
+        systemctl("stop postgresql.service")
+        succeed(
+            "curl -sSf http://localhost:9187/metrics | grep -v 'pg_exporter_last_scrape_error 0'"
+        )
+        succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 0'")
+        systemctl("start postgresql.service")
+        wait_for_unit("postgresql.service")
+        succeed(
+            "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
+        )
+        succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
+      '';
+    };
+
+    process = {
+      exporterConfig = {
+        enable = true;
+        settings.process_names = [
+          # Remove nix store path from process name
+          { name = "{{.Matches.Wrapped}} {{ .Matches.Args }}"; cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ]; }
+        ];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-process-exporter.service")
+        wait_for_open_port(9256)
+        wait_until_succeeds(
+            "curl -sSf localhost:9256/metrics | grep -q '{}'".format(
+                'namedprocess_namegroup_cpu_seconds_total{groupname="process-exporter '
+            )
+        )
+      '';
+    };
+
+    pve = let
+      pveExporterEnvFile = pkgs.writeTextFile {
+        name = "pve.env";
+        text = ''
+          PVE_USER="test_user@pam"
+          PVE_PASSWORD="hunter3"
+          PVE_VERIFY_SSL="false"
+        '';
+      };
+    in {
+      exporterConfig = {
+        enable = true;
+        environmentFile = pveExporterEnvFile;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-pve-exporter.service")
+        wait_for_open_port(9221)
+        wait_until_succeeds("curl localhost:9221")
+      '';
+    };
+
+    py-air-control = {
+      nodeName = "py_air_control";
+      exporterConfig = {
+        enable = true;
+        deviceHostname = "127.0.0.1";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-py-air-control-exporter.service")
+        wait_for_open_port(9896)
+        succeed(
+            "curl -sSf http://localhost:9896/metrics | grep 'py_air_control_sampling_error_total'"
+        )
+      '';
+    };
+
+    redis = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider.services.redis.servers."".enable = true;
+      exporterTest = ''
+        wait_for_unit("redis.service")
+        wait_for_unit("prometheus-redis-exporter.service")
+        wait_for_open_port(6379)
+        wait_for_open_port(9121)
+        wait_until_succeeds("curl -sSf localhost:9121/metrics | grep 'redis_up 1'")
+      '';
+    };
+
+    restic =
+      let
+        repository = "rest:http://127.0.0.1:8000";
+        passwordFile = pkgs.writeText "restic-test-password" "test-password";
+      in
+      {
+        exporterConfig = {
+          enable = true;
+          inherit repository passwordFile;
+        };
+        metricProvider = {
+          services.restic.server = {
+            enable = true;
+            extraFlags = [ "--no-auth" ];
+          };
+          environment.systemPackages = [ pkgs.restic ];
+        };
+        exporterTest = ''
+          # prometheus-restic-exporter.service fails without initialised repository
+          systemctl("stop prometheus-restic-exporter.service")
+
+          # Initialise the repository
+          wait_for_unit("restic-rest-server.service")
+          wait_for_open_port(8000)
+          succeed("restic init --repo ${repository} --password-file ${passwordFile}")
+
+          systemctl("start prometheus-restic-exporter.service")
+          wait_for_unit("prometheus-restic-exporter.service")
+          wait_for_open_port(9753)
+          wait_until_succeeds("curl -sSf localhost:9753/metrics | grep 'restic_check_success 1.0'")
+        '';
+      };
+
+    rspamd = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.rspamd.enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("rspamd.service")
+        wait_for_unit("prometheus-rspamd-exporter.service")
+        wait_for_open_port(11334)
+        wait_for_open_port(7980)
+        wait_until_succeeds(
+            "curl -sSf 'localhost:7980/probe?target=http://localhost:11334/stat' | grep 'rspamd_scanned{host=\"rspamd\"} 0'"
+        )
+      '';
+    };
+
+    rtl_433 = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        # Mock rtl_433 binary to return a dummy metric stream.
+        nixpkgs.overlays = [
+          (self: super: {
+            rtl_433 = self.runCommand "rtl_433" { } ''
+              mkdir -p "$out/bin"
+              cat <<EOF > "$out/bin/rtl_433"
+              #!/bin/sh
+              while true; do
+                printf '{"time" : "2020-04-26 13:37:42", "model" : "zopieux", "id" : 55, "channel" : 3, "temperature_C" : 18.000}\n'
+                sleep 4
+              done
+              EOF
+              chmod +x "$out/bin/rtl_433"
+            '';
+          })
+        ];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-rtl_433-exporter.service")
+        wait_for_open_port(9550)
+        wait_until_succeeds(
+            "curl -sSf localhost:9550/metrics | grep '{}'".format(
+                'rtl_433_temperature_celsius{channel="3",id="55",location="",model="zopieux"} 18'
+            )
+        )
+      '';
+    };
+
+    sabnzbd = {
+      exporterConfig = {
+        enable = true;
+        servers = [{
+          baseUrl = "http://localhost:8080";
+          apiKeyFile = "/var/sabnzbd-apikey";
+        }];
+      };
+
+      metricProvider = {
+        services.sabnzbd.enable = true;
+
+        # unrar is required for sabnzbd
+        nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "unrar" ];
+
+        # extract the generated api key before starting
+        systemd.services.sabnzbd-apikey = {
+          requires = [ "sabnzbd.service" ];
+          after = [ "sabnzbd.service" ];
+          requiredBy = [ "prometheus-sabnzbd-exporter.service" ];
+          before = [ "prometheus-sabnzbd-exporter.service" ];
+          script = ''
+            grep -Po '^api_key = \K.+' /var/lib/sabnzbd/sabnzbd.ini > /var/sabnzbd-apikey
+          '';
+        };
+      };
+
+      exporterTest = ''
+        wait_for_unit("sabnzbd.service")
+        wait_for_unit("prometheus-sabnzbd-exporter.service")
+        wait_for_open_port(8080)
+        wait_for_open_port(9387)
+        wait_until_succeeds(
+            "curl -sSf 'localhost:9387/metrics' | grep 'sabnzbd_queue_size{sabnzbd_instance=\"http://localhost:8080\"} 0.0'"
+        )
+      '';
+    };
+
+    scaphandre = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        boot.kernelModules = [ "intel_rapl_common" ];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-scaphandre-exporter.service")
+        wait_for_open_port(8080)
+        wait_until_succeeds(
+            "curl -sSf 'localhost:8080/metrics'"
+        )
+      '';
+    };
+
+    shelly = {
+      exporterConfig = {
+        enable = true;
+        metrics-file = "${pkgs.writeText "test.json" ''{}''}";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-shelly-exporter.service")
+        wait_for_open_port(9784)
+        wait_until_succeeds(
+            "curl -sSf 'localhost:9784/metrics'"
+        )
+      '';
+    };
+
+    script = {
+      exporterConfig = {
+        enable = true;
+        settings.scripts = [
+          { name = "success"; script = "sleep 1"; }
+        ];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-script-exporter.service")
+        wait_for_open_port(9172)
+        wait_until_succeeds(
+            "curl -sSf 'localhost:9172/probe?name=success' | grep -q '{}'".format(
+                'script_success{script="success"} 1'
+            )
+        )
+      '';
+    };
+
+    smartctl = {
+      exporterConfig = {
+        enable = true;
+        devices = [
+          "/dev/vda"
+        ];
+      };
+      exporterTest = ''
+        wait_until_succeeds(
+            'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Unable to detect device type"'
+        )
+      '';
+    };
+
+    smokeping = {
+      exporterConfig = {
+        enable = true;
+        hosts = [ "127.0.0.1" ];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-smokeping-exporter.service")
+        wait_for_open_port(9374)
+        wait_until_succeeds(
+            "curl -sSf localhost:9374/metrics | grep '{}' | grep -v ' 0$'".format(
+                'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1",source=""} '
+            )
+        )
+        wait_until_succeeds(
+            "curl -sSf localhost:9374/metrics | grep '{}'".format(
+                'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1",source=""}'
+            )
+        )
+      '';
+    };
+
+    snmp = {
+      exporterConfig = {
+        enable = true;
+        configuration = {
+          auths.public_v2 = {
+            community = "public";
+            version = 2;
+          };
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-snmp-exporter.service")
+        wait_for_open_port(9116)
+        succeed("curl -sSf localhost:9116/metrics | grep 'snmp_request_errors_total 0'")
+      '';
+    };
+
+    sql = {
+      exporterConfig = {
+        configuration.jobs.points = {
+          interval = "1m";
+          connections = [
+            "postgres://prometheus-sql-exporter@/data?host=/run/postgresql&sslmode=disable"
+          ];
+          queries = {
+            points = {
+              labels = [ "name" ];
+              help = "Amount of points accumulated per person";
+              values = [ "amount" ];
+              query = "SELECT SUM(amount) as amount, name FROM points GROUP BY name";
+            };
+          };
+        };
+        enable = true;
+        user = "prometheus-sql-exporter";
+      };
+      metricProvider = {
+        services.postgresql = {
+          enable = true;
+          initialScript = builtins.toFile "init.sql" ''
+            CREATE DATABASE data;
+            \c data;
+            CREATE TABLE points (amount INT, name TEXT);
+            INSERT INTO points(amount, name) VALUES (1, 'jack');
+            INSERT INTO points(amount, name) VALUES (2, 'jill');
+            INSERT INTO points(amount, name) VALUES (3, 'jack');
+
+            CREATE USER "prometheus-sql-exporter";
+            GRANT ALL PRIVILEGES ON DATABASE data TO "prometheus-sql-exporter";
+            GRANT SELECT ON points TO "prometheus-sql-exporter";
+          '';
+        };
+        systemd.services.prometheus-sql-exporter.after = [ "postgresql.service" ];
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-sql-exporter.service")
+        wait_for_open_port(9237)
+        succeed("curl http://localhost:9237/metrics | grep -c 'sql_points{' | grep 2")
+      '';
+    };
+
+    statsd = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-statsd-exporter.service")
+        wait_for_open_port(9102)
+        succeed("curl http://localhost:9102/metrics | grep 'statsd_exporter_build_info{'")
+        wait_until_succeeds(
+          "echo 'test.udp:1|c' > /dev/udp/localhost/9125 && \
+          curl http://localhost:9102/metrics | grep 'test_udp 1'",
+          timeout=10
+        )
+        wait_until_succeeds(
+          "echo 'test.tcp:1|c' > /dev/tcp/localhost/9125 && \
+          curl http://localhost:9102/metrics | grep 'test_tcp 1'",
+          timeout=10
+        )
+      '';
+    };
+
+    surfboard = {
+      exporterConfig = {
+        enable = true;
+        modemAddress = "localhost";
+      };
+      metricProvider = {
+        systemd.services.prometheus-surfboard-exporter.after = [ "nginx.service" ];
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost.locations."/cgi-bin/status".extraConfig = ''
+            return 204;
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("nginx.service")
+        wait_for_open_port(80)
+        wait_for_unit("prometheus-surfboard-exporter.service")
+        wait_for_open_port(9239)
+        succeed("curl -sSf localhost:9239/metrics | grep 'surfboard_up 1'")
+      '';
+    };
+
+    systemd = {
+      exporterConfig = {
+        enable = true;
+
+        extraFlags = [
+          "--systemd.collector.enable-restart-count"
+        ];
+      };
+      metricProvider = { };
+      exporterTest = ''
+        wait_for_unit("prometheus-systemd-exporter.service")
+        wait_for_open_port(9558)
+        wait_until_succeeds(
+            "curl -sSf localhost:9558/metrics | grep '{}'".format(
+                'systemd_unit_state{name="basic.target",state="active",type="target"} 1'
+            )
+        )
+        succeed(
+            "curl -sSf localhost:9558/metrics | grep '{}'".format(
+                'systemd_service_restart_total{name="prometheus-systemd-exporter.service"} 0'
+            )
+        )
+      '';
+    };
+
+    tor = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        # Note: this does not connect the test environment to the Tor network.
+        # Client, relay, bridge or exit connectivity are disabled by default.
+        services.tor.enable = true;
+        services.tor.settings.ControlPort = 9051;
+      };
+      exporterTest = ''
+        wait_for_unit("tor.service")
+        wait_for_open_port(9051)
+        wait_for_unit("prometheus-tor-exporter.service")
+        wait_for_open_port(9130)
+        succeed("curl -sSf localhost:9130/metrics | grep 'tor_version{.\\+} 1'")
+      '';
+    };
+
+    unpoller = {
+      nodeName = "unpoller";
+      exporterConfig.enable = true;
+      exporterConfig.controllers = [{ }];
+      exporterTest = ''
+        wait_until_succeeds(
+            'journalctl -eu prometheus-unpoller-exporter.service -o cat | grep "Connection Error"'
+        )
+      '';
+    };
+
+    unbound = {
+      exporterConfig = {
+        enable = true;
+        unbound.host = "unix:///run/unbound/unbound.ctl";
+      };
+      metricProvider = {
+        services.unbound = {
+          enable = true;
+          localControlSocketPath = "/run/unbound/unbound.ctl";
+        };
+        systemd.services.prometheus-unbound-exporter.serviceConfig = {
+          SupplementaryGroups = [ "unbound" ];
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("unbound.service")
+        wait_for_unit("prometheus-unbound-exporter.service")
+        wait_for_open_port(9167)
+        wait_until_succeeds("curl -sSf localhost:9167/metrics | grep 'unbound_up 1'")
+      '';
+    };
+
+    v2ray = {
+      exporterConfig = {
+        enable = true;
+      };
+
+      metricProvider = {
+        systemd.services.prometheus-nginx-exporter.after = [ "v2ray.service" ];
+        services.v2ray = {
+          enable = true;
+          config = {
+            stats = {};
+            api = {
+              tag = "api";
+              services = [ "StatsService" ];
+            };
+            inbounds = [
+              {
+                port = 1080;
+                listen = "127.0.0.1";
+                protocol = "http";
+              }
+              {
+                listen = "127.0.0.1";
+                port = 54321;
+                protocol = "dokodemo-door";
+                settings = { address = "127.0.0.1"; };
+                tag = "api";
+              }
+            ];
+            outbounds = [
+              {
+                protocol = "freedom";
+              }
+              {
+                protocol = "freedom";
+                settings = {};
+                tag = "api";
+              }
+            ];
+            routing = {
+              strategy = "rules";
+              settings = {
+                rules = [
+                  {
+                    inboundTag = [ "api" ];
+                    outboundTag = "api";
+                    type = "field";
+                  }
+                ];
+              };
+            };
+          };
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-v2ray-exporter.service")
+        wait_for_open_port(9299)
+        succeed("curl -sSf localhost:9299/scrape | grep 'v2ray_up 1'")
+      '';
+    };
+
+    varnish = {
+      exporterConfig = {
+        enable = true;
+        instance = "/var/spool/varnish/varnish";
+        group = "varnish";
+      };
+      metricProvider = {
+        systemd.services.prometheus-varnish-exporter.after = [
+          "varnish.service"
+        ];
+        services.varnish = {
+          enable = true;
+          config = ''
+            vcl 4.0;
+            backend default {
+              .host = "127.0.0.1";
+              .port = "80";
+            }
+          '';
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-varnish-exporter.service")
+        wait_for_open_port(6081)
+        wait_for_open_port(9131)
+        succeed("curl -sSf http://localhost:9131/metrics | grep 'varnish_up 1'")
+      '';
+    };
+
+    wireguard = let
+      snakeoil = import ./wireguard/snakeoil-keys.nix;
+      publicKeyWithoutNewlines = replaceStrings [ "\n" ] [ "" ] snakeoil.peer1.publicKey;
+    in
+      {
+        exporterConfig.enable = true;
+        metricProvider = {
+          networking.wireguard.interfaces.wg0 = {
+            ips = [ "10.23.42.1/32" "fc00::1/128" ];
+            listenPort = 23542;
+
+            inherit (snakeoil.peer0) privateKey;
+
+            peers = singleton {
+              allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
+
+              inherit (snakeoil.peer1) publicKey;
+            };
+          };
+          systemd.services.prometheus-wireguard-exporter.after = [ "wireguard-wg0.service" ];
+        };
+        exporterTest = ''
+          wait_for_unit("prometheus-wireguard-exporter.service")
+          wait_for_open_port(9586)
+          wait_until_succeeds(
+              "curl -sSf http://localhost:9586/metrics | grep '${publicKeyWithoutNewlines}'"
+          )
+        '';
+      };
+
+    zfs = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        boot.supportedFilesystems = [ "zfs" ];
+        networking.hostId = "7327ded7";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-zfs-exporter.service")
+        wait_for_unit("zfs.target")
+        wait_for_open_port(9134)
+        wait_until_succeeds("curl -f localhost:9134/metrics | grep 'zfs_scrape_collector_success{.*} 1'")
+      '';
+    };
+  };
+in
+mapAttrs
+  (exporter: testConfig: (makeTest (
+    let
+      nodeName = testConfig.nodeName or exporter;
+
+    in
+    {
+      name = "prometheus-${exporter}-exporter";
+
+      nodes.${nodeName} = mkMerge [{
+        services.prometheus.exporters.${exporter} = testConfig.exporterConfig;
+      } testConfig.metricProvider or { }];
+
+      testScript = ''
+        ${nodeName}.start()
+        ${concatStringsSep "\n" (map (line:
+          if builtins.any (b: b) [
+            (builtins.match "^[[:space:]]*$" line != null)
+            (builtins.substring 0 1 line == "#")
+            (builtins.substring 0 1 line == " ")
+            (builtins.substring 0 1 line == ")")
+          ]
+          then line
+          else "${nodeName}.${line}"
+        ) (splitString "\n" (removeSuffix "\n" testConfig.exporterTest)))}
+        ${nodeName}.shutdown()
+      '';
+
+      meta = with maintainers; {
+        maintainers = [ willibutz ];
+      };
+    }
+  )))
+  exporterTests
diff --git a/nixpkgs/nixos/tests/prometheus.nix b/nixpkgs/nixos/tests/prometheus.nix
new file mode 100644
index 000000000000..011127389377
--- /dev/null
+++ b/nixpkgs/nixos/tests/prometheus.nix
@@ -0,0 +1,349 @@
+let
+  grpcPort   = 19090;
+  queryPort  =  9090;
+  minioPort  =  9000;
+  pushgwPort =  9091;
+  frontPort  =  9092;
+
+  s3 = {
+    accessKey = "BKIKJAA5BMMU2RHO6IBB";
+    secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
+  };
+
+  objstore.config = {
+    type = "S3";
+    config = {
+      bucket = "thanos-bucket";
+      endpoint = "s3:${toString minioPort}";
+      region =  "us-east-1";
+      access_key = s3.accessKey;
+      secret_key = s3.secretKey;
+      insecure = true;
+      signature_version2 = false;
+      put_user_metadata = {};
+      http_config = {
+        idle_conn_timeout = "0s";
+        insecure_skip_verify = false;
+      };
+      trace = {
+        enable = false;
+      };
+    };
+  };
+
+in import ./make-test-python.nix {
+  name = "prometheus";
+
+  nodes = {
+    prometheus = { pkgs, ... }: {
+      virtualisation.diskSize = 2 * 1024;
+      virtualisation.memorySize = 2048;
+      environment.systemPackages = [ pkgs.jq ];
+      networking.firewall.allowedTCPPorts = [ grpcPort ];
+      services.prometheus = {
+        enable = true;
+        enableReload = true;
+        scrapeConfigs = [
+          {
+            job_name = "prometheus";
+            static_configs = [
+              {
+                targets = [ "127.0.0.1:${toString queryPort}" ];
+                labels = { instance = "localhost"; };
+              }
+            ];
+          }
+          {
+            job_name = "pushgateway";
+            scrape_interval = "1s";
+            static_configs = [
+              {
+                targets = [ "127.0.0.1:${toString pushgwPort}" ];
+              }
+            ];
+          }
+        ];
+        rules = [
+          ''
+            groups:
+              - name: test
+                rules:
+                  - record: testrule
+                    expr: count(up{job="prometheus"})
+          ''
+        ];
+        globalConfig = {
+          external_labels = {
+            some_label = "required by thanos";
+          };
+        };
+        extraFlags = [
+          # Required by thanos
+          "--storage.tsdb.min-block-duration=5s"
+          "--storage.tsdb.max-block-duration=5s"
+        ];
+      };
+      services.prometheus.pushgateway = {
+        enable = true;
+        web.listen-address = ":${toString pushgwPort}";
+        persistMetrics = true;
+        persistence.interval = "1s";
+        stateDir = "prometheus-pushgateway";
+      };
+      services.thanos = {
+        sidecar = {
+          enable = true;
+          grpc-address = "0.0.0.0:${toString grpcPort}";
+          inherit objstore;
+        };
+
+        # TODO: Add some tests for these services:
+        #rule = {
+        #  enable = true;
+        #  http-address = "0.0.0.0:19194";
+        #  grpc-address = "0.0.0.0:19193";
+        #  query.addresses = [
+        #    "localhost:19191"
+        #  ];
+        #  labels = {
+        #    just = "some";
+        #    nice = "labels";
+        #  };
+        #};
+        #
+        #receive = {
+        #  http-address = "0.0.0.0:19195";
+        #  enable = true;
+        #  labels = {
+        #    just = "some";
+        #    nice = "labels";
+        #  };
+        #};
+      };
+      # Adds a "specialisation" of the above config which allows us to
+      # "switch" to it and see if the services.prometheus.enableReload
+      # functionality actually reloads the prometheus service instead of
+      # restarting it.
+      specialisation = {
+        "prometheus-config-change" = {
+          configuration = {
+            environment.systemPackages = [ pkgs.yq ];
+
+            # This configuration just adds a new prometheus job
+            # to scrape the node_exporter metrics of the s3 machine.
+            services.prometheus = {
+              scrapeConfigs = [
+                {
+                  job_name = "s3-node_exporter";
+                  static_configs = [
+                    {
+                      targets = [ "s3:9100" ];
+                    }
+                  ];
+                }
+              ];
+            };
+          };
+        };
+      };
+    };
+
+    query = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      services.thanos.query = {
+        enable = true;
+        http-address = "0.0.0.0:${toString queryPort}";
+        endpoints = [
+          "prometheus:${toString grpcPort}"
+        ];
+      };
+      services.thanos.query-frontend = {
+        enable = true;
+        http-address = "0.0.0.0:${toString frontPort}";
+        query-frontend.downstream-url = "http://127.0.0.1:${toString queryPort}";
+      };
+    };
+
+    store = { pkgs, ... }: {
+      virtualisation.diskSize = 2 * 1024;
+      virtualisation.memorySize = 2048;
+      environment.systemPackages = with pkgs; [ jq thanos ];
+      services.thanos.store = {
+        enable = true;
+        http-address = "0.0.0.0:10902";
+        grpc-address = "0.0.0.0:${toString grpcPort}";
+        inherit objstore;
+        sync-block-duration = "1s";
+      };
+      services.thanos.compact = {
+        enable = true;
+        http-address = "0.0.0.0:10903";
+        inherit objstore;
+        consistency-delay = "5s";
+      };
+      services.thanos.query = {
+        enable = true;
+        http-address = "0.0.0.0:${toString queryPort}";
+        endpoints = [
+          "localhost:${toString grpcPort}"
+        ];
+      };
+    };
+
+    s3 = { pkgs, ... } : {
+      # Minio requires at least 1GiB of free disk space to run.
+      virtualisation = {
+        diskSize = 2 * 1024;
+      };
+      networking.firewall.allowedTCPPorts = [ minioPort ];
+
+      services.minio = {
+        enable = true;
+        inherit (s3) accessKey secretKey;
+      };
+
+      environment.systemPackages = [ pkgs.minio-client ];
+
+      services.prometheus.exporters.node = {
+        enable = true;
+        openFirewall = true;
+      };
+    };
+  };
+
+  testScript = { nodes, ... } : ''
+    import json
+
+    # Before starting the other machines we first make sure that our S3 service is online
+    # and has a bucket added for thanos:
+    s3.start()
+    s3.wait_for_unit("minio.service")
+    s3.wait_for_open_port(${toString minioPort})
+    s3.succeed(
+        "mc config host add minio "
+        + "http://localhost:${toString minioPort} "
+        + "${s3.accessKey} ${s3.secretKey} --api s3v4",
+        "mc mb minio/thanos-bucket",
+    )
+
+    # Now that s3 has started we can start the other machines:
+    for machine in prometheus, query, store:
+        machine.start()
+
+    # Check if prometheus responds to requests:
+    prometheus.wait_for_unit("prometheus.service")
+
+    prometheus.wait_for_open_port(${toString queryPort})
+    prometheus.succeed("curl -sf http://127.0.0.1:${toString queryPort}/metrics")
+
+    # Let's test if pushing a metric to the pushgateway succeeds:
+    prometheus.wait_for_unit("pushgateway.service")
+    prometheus.succeed(
+        "echo 'some_metric 3.14' | "
+        + "curl -f --data-binary \@- "
+        + "http://127.0.0.1:${toString pushgwPort}/metrics/job/some_job"
+    )
+
+    # Now check whether that metric gets ingested by prometheus.
+    # Since we'll check for the metric several times on different machines
+    # we abstract the test using the following function:
+
+    # Function to check if the metric "some_metric" has been received and returns the correct value.
+    def wait_for_metric(machine):
+        return machine.wait_until_succeeds(
+            "curl -sf 'http://127.0.0.1:${toString queryPort}/api/v1/query?query=some_metric' | "
+            + "jq '.data.result[0].value[1]' | grep '\"3.14\"'"
+        )
+
+
+    wait_for_metric(prometheus)
+
+    # Let's test if the pushgateway persists metrics to the configured location.
+    prometheus.wait_until_succeeds("test -e /var/lib/prometheus-pushgateway/metrics")
+
+    # Test thanos
+    prometheus.wait_for_unit("thanos-sidecar.service")
+
+    # Test if the Thanos query service can correctly retrieve the metric that was send above.
+    query.wait_for_unit("thanos-query.service")
+    wait_for_metric(query)
+
+    # Test Thanos query frontend service
+    query.wait_for_unit("thanos-query-frontend.service")
+    query.succeed("curl -sS http://localhost:${toString frontPort}/-/healthy")
+
+    # Test if the Thanos sidecar has correctly uploaded its TSDB to S3, if the
+    # Thanos storage service has correctly downloaded it from S3 and if the Thanos
+    # query service running on $store can correctly retrieve the metric:
+    store.wait_for_unit("thanos-store.service")
+    wait_for_metric(store)
+
+    store.wait_for_unit("thanos-compact.service")
+
+    # Test if the Thanos bucket command is able to retrieve blocks from the S3 bucket
+    # and check if the blocks have the correct labels:
+    store.succeed(
+        "thanos tools bucket ls "
+        + "--objstore.config-file=${nodes.store.config.services.thanos.store.objstore.config-file} "
+        + "--output=json | "
+        + "jq .thanos.labels.some_label | "
+        + "grep 'required by thanos'"
+    )
+
+    # Check if switching to a NixOS configuration that changes the prometheus
+    # configuration reloads (instead of restarts) prometheus before the switch
+    # finishes successfully:
+    with subtest("config change reloads prometheus"):
+        # We check if prometheus has finished reloading by looking for the message
+        # "Completed loading of configuration file" in the journal between the start
+        # and finish of switching to the new NixOS configuration.
+        #
+        # To mark the start we record the journal cursor before starting the switch:
+        cursor_before_switching = json.loads(
+            prometheus.succeed("journalctl -n1 -o json --output-fields=__CURSOR")
+        )["__CURSOR"]
+
+        # Now we switch:
+        prometheus_config_change = prometheus.succeed(
+            "readlink /run/current-system/specialisation/prometheus-config-change"
+        ).strip()
+        prometheus.succeed(prometheus_config_change + "/bin/switch-to-configuration test")
+
+        # Next we retrieve all logs since the start of switching:
+        logs_after_starting_switching = prometheus.succeed(
+            """
+              journalctl --after-cursor='{cursor_before_switching}' -o json --output-fields=MESSAGE
+            """.format(
+                cursor_before_switching=cursor_before_switching
+            )
+        )
+
+        # Finally we check if the message "Completed loading of configuration file"
+        # occurs before the "finished switching to system configuration" message:
+        finished_switching_msg = (
+            "finished switching to system configuration " + prometheus_config_change
+        )
+        reloaded_before_switching_finished = False
+        finished_switching = False
+        for log_line in logs_after_starting_switching.split("\n"):
+            msg = json.loads(log_line)["MESSAGE"]
+            if "Completed loading of configuration file" in msg:
+                reloaded_before_switching_finished = True
+            if msg == finished_switching_msg:
+                finished_switching = True
+                break
+
+        assert reloaded_before_switching_finished
+        assert finished_switching
+
+        # Check if the reloaded config includes the new s3-node_exporter job:
+        prometheus.succeed(
+          """
+            curl -sf http://127.0.0.1:${toString queryPort}/api/v1/status/config \
+              | jq -r .data.yaml \
+              | yq '.scrape_configs | any(.job_name == "s3-node_exporter")' \
+              | grep true
+          """
+        )
+  '';
+}
diff --git a/nixpkgs/nixos/tests/promscale.nix b/nixpkgs/nixos/tests/promscale.nix
new file mode 100644
index 000000000000..da18628f2482
--- /dev/null
+++ b/nixpkgs/nixos/tests/promscale.nix
@@ -0,0 +1,60 @@
+# mostly copied from ./timescaledb.nix which was copied from ./postgresql.nix
+# as it seemed unapproriate to test additional extensions for postgresql there.
+
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE USER promscale SUPERUSER PASSWORD 'promscale';
+    CREATE DATABASE promscale OWNER promscale;
+  '';
+
+  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ anpin ];
+    };
+
+    nodes.machine = { config, pkgs, ... }:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          extraPlugins = ps: with ps; [
+            timescaledb
+            promscale_extension
+          ];
+          settings = { shared_preload_libraries = "timescaledb, promscale"; };
+        };
+        environment.systemPackages = with pkgs; [ promscale ];
+      };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("postgresql")
+      with subtest("Postgresql with extensions timescaledb and promscale is available just after unit start"):
+          print(machine.succeed("sudo -u postgres psql -f ${test-sql}"))
+          machine.succeed("sudo -u postgres psql promscale -c 'SHOW shared_preload_libraries;' | grep promscale")
+          machine.succeed(
+            "promscale --db.name promscale --db.password promscale --db.user promscale --db.ssl-mode allow --startup.install-extensions --startup.only"
+          )
+      machine.succeed("sudo -u postgres psql promscale -c 'SELECT ps_trace.get_trace_retention_period();' | grep '(1 row)'")
+      machine.shutdown()
+    '';
+  };
+  #version 15 is not supported yet
+  applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12" && !(versionAtLeast value.version "15")) postgresql-versions;
+in
+mapAttrs'
+  (name: package: {
+    inherit name;
+    value = make-postgresql-test name package;
+  })
+  applicablePostgresqlVersions
diff --git a/nixpkgs/nixos/tests/prowlarr.nix b/nixpkgs/nixos/tests/prowlarr.nix
new file mode 100644
index 000000000000..663743546459
--- /dev/null
+++ b/nixpkgs/nixos/tests/prowlarr.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "prowlarr";
+  meta.maintainers = with lib.maintainers; [ jdreaver ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.prowlarr.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("prowlarr.service")
+    machine.wait_for_open_port(9696)
+    response = machine.succeed("curl --fail http://localhost:9696/")
+    assert '<title>Prowlarr</title>' in response, "Login page didn't load successfully"
+    machine.succeed("[ -d /var/lib/prowlarr ]")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/proxy.nix b/nixpkgs/nixos/tests/proxy.nix
new file mode 100644
index 000000000000..f8a3d576903e
--- /dev/null
+++ b/nixpkgs/nixos/tests/proxy.nix
@@ -0,0 +1,90 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  backend = { pkgs, ... }: {
+    services.httpd = {
+      enable = true;
+      adminAddr = "foo@example.org";
+      virtualHosts.localhost.documentRoot = "${pkgs.valgrind.doc}/share/doc/valgrind/html";
+    };
+    networking.firewall.allowedTCPPorts = [ 80 ];
+  };
+in {
+  name = "proxy";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes = {
+    proxy = { nodes, ... }: {
+      services.httpd = {
+        enable = true;
+        adminAddr = "bar@example.org";
+        extraModules = [ "proxy_balancer" "lbmethod_byrequests" ];
+        extraConfig = ''
+          ExtendedStatus on
+        '';
+        virtualHosts.localhost = {
+          extraConfig = ''
+            <Location /server-status>
+              Require all granted
+              SetHandler server-status
+            </Location>
+
+            <Proxy balancer://cluster>
+              Require all granted
+              BalancerMember http://${nodes.backend1.config.networking.hostName} retry=0
+              BalancerMember http://${nodes.backend2.config.networking.hostName} retry=0
+            </Proxy>
+
+            ProxyStatus       full
+            ProxyPass         /server-status !
+            ProxyPass         /       balancer://cluster/
+            ProxyPassReverse  /       balancer://cluster/
+
+            # For testing; don't want to wait forever for dead backend servers.
+            ProxyTimeout      5
+          '';
+        };
+      };
+      networking.firewall.allowedTCPPorts = [ 80 ];
+    };
+
+    backend1 = backend;
+    backend2 = backend;
+
+    client = { ... }: { };
+  };
+
+  testScript = ''
+    start_all()
+
+    proxy.wait_for_unit("httpd")
+    backend1.wait_for_unit("httpd")
+    backend2.wait_for_unit("httpd")
+    client.wait_for_unit("network.target")
+
+    # With the back-ends up, the proxy should work.
+    client.succeed("curl --fail http://proxy/")
+
+    client.succeed("curl --fail http://proxy/server-status")
+
+    # Block the first back-end.
+    backend1.block()
+
+    # The proxy should still work.
+    client.succeed("curl --fail http://proxy/")
+    client.succeed("curl --fail http://proxy/")
+
+    # Block the second back-end.
+    backend2.block()
+
+    # Now the proxy should fail as well.
+    client.fail("curl --fail http://proxy/")
+
+    # But if the second back-end comes back, the proxy should start
+    # working again.
+    backend2.unblock()
+    client.succeed("curl --fail http://proxy/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pt2-clone.nix b/nixpkgs/nixos/tests/pt2-clone.nix
new file mode 100644
index 000000000000..ea4329c4a980
--- /dev/null
+++ b/nixpkgs/nixos/tests/pt2-clone.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "pt2-clone";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    sound.enable = true;
+    environment.systemPackages = [ pkgs.pt2-clone ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      # Add a dummy sound card, or the program won't start
+      machine.execute("modprobe snd-dummy")
+
+      machine.execute("pt2-clone >&2 &")
+
+      machine.wait_for_window(r"ProTracker")
+      machine.sleep(5)
+      # One of the few words that actually get recognized
+      if "LENGTH" not in machine.get_screen_text():
+          raise Exception("Program did not start successfully")
+      machine.screenshot("screen")
+    '';
+})
+
diff --git a/nixpkgs/nixos/tests/public-inbox.nix b/nixpkgs/nixos/tests/public-inbox.nix
new file mode 100644
index 000000000000..4d06d3e1738e
--- /dev/null
+++ b/nixpkgs/nixos/tests/public-inbox.nix
@@ -0,0 +1,230 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  orga = "example";
+  domain = "${orga}.localdomain";
+
+  tls-cert = pkgs.runCommand "selfSignedCert" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
+      -subj '/CN=machine.${domain}'
+    install -D -t $out key.pem cert.pem
+  '';
+in
+{
+  name = "public-inbox";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ julm ];
+
+  nodes.machine = { config, pkgs, nodes, ... }: let
+    inherit (config.services) gitolite public-inbox;
+    # Git repositories paths in Gitolite.
+    # Only their baseNameOf is used for configuring public-inbox.
+    repositories = [
+      "user/repo1"
+      "user/repo2"
+    ];
+  in
+  {
+    virtualisation.diskSize = 1 * 1024;
+    virtualisation.memorySize = 1 * 1024;
+    networking.domain = domain;
+
+    security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+    # If using security.acme:
+    #security.acme.certs."${domain}".postRun = ''
+    #  systemctl try-restart public-inbox-nntpd public-inbox-imapd
+    #'';
+
+    services.public-inbox = {
+      enable = true;
+      postfix.enable = true;
+      openFirewall = true;
+      settings.publicinbox = {
+        css = [ "href=https://machine.${domain}/style/light.css" ];
+        nntpserver = [ "nntps://machine.${domain}" ];
+        wwwlisting = "match=domain";
+      };
+      mda = {
+        enable = true;
+        args = [ "--no-precheck" ]; # Allow Bcc:
+      };
+      http = {
+        enable = true;
+        port = "/run/public-inbox-http.sock";
+        #port = 8080;
+        args = ["-W0"];
+        mounts = [
+          "https://machine.${domain}/inbox"
+        ];
+      };
+      nntp = {
+        enable = true;
+        #port = 563;
+        args = ["-W0"];
+        cert = "${tls-cert}/cert.pem";
+        key = "${tls-cert}/key.pem";
+      };
+      imap = {
+        enable = true;
+        #port = 993;
+        args = ["-W0"];
+        cert = "${tls-cert}/cert.pem";
+        key = "${tls-cert}/key.pem";
+      };
+      inboxes = lib.recursiveUpdate (lib.genAttrs (map baseNameOf repositories) (repo: {
+        address = [
+          # Routed to the "public-inbox:" transport in services.postfix.transport
+          "${repo}@${domain}"
+        ];
+        description = ''
+          ${repo}@${domain} :
+          discussions about ${repo}.
+        '';
+        url = "https://machine.${domain}/inbox/${repo}";
+        newsgroup = "inbox.comp.${orga}.${repo}";
+        coderepo = [ repo ];
+      }))
+      {
+        repo2 = {
+          hide = [
+            "imap" # FIXME: doesn't work for IMAP as of public-inbox 1.6.1
+            "manifest"
+            "www"
+          ];
+        };
+      };
+      settings.coderepo = lib.listToAttrs (map (path: lib.nameValuePair (baseNameOf path) {
+        dir = "/var/lib/gitolite/repositories/${path}.git";
+        cgitUrl = "https://git.${domain}/${path}.git";
+      }) repositories);
+    };
+
+    # Use gitolite to store Git repositories listed in coderepo entries
+    services.gitolite = {
+      enable = true;
+      adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmoTOQnGqX+//us5oye8UuE+tQBx9QEM7PN13jrwgqY root@localhost";
+    };
+    systemd.services.public-inbox-httpd = {
+      serviceConfig.SupplementaryGroups = [ gitolite.group ];
+    };
+
+    # Use nginx as a reverse proxy for public-inbox-httpd
+    services.nginx = {
+      enable = true;
+      recommendedGzipSettings = true;
+      recommendedOptimisation = true;
+      recommendedTlsSettings = true;
+      recommendedProxySettings = true;
+      virtualHosts."machine.${domain}" = {
+        forceSSL = true;
+        sslCertificate = "${tls-cert}/cert.pem";
+        sslCertificateKey = "${tls-cert}/key.pem";
+        locations."/".return = "302 /inbox";
+        locations."= /inbox".return = "302 /inbox/";
+        locations."/inbox".proxyPass = "http://unix:${public-inbox.http.port}:/inbox";
+        # If using TCP instead of a Unix socket:
+        #locations."/inbox".proxyPass = "http://127.0.0.1:${toString public-inbox.http.port}/inbox";
+        # Referred to by settings.publicinbox.css
+        # See http://public-inbox.org/meta/_/text/color/
+        locations."= /style/light.css".alias = pkgs.writeText "light.css" ''
+          * { background:#fff; color:#000 }
+
+          a { color:#00f; text-decoration:none }
+          a:visited { color:#808 }
+
+          *.q { color:#008 }
+
+          *.add { color:#060 }
+          *.del {color:#900 }
+          *.head { color:#000 }
+          *.hunk { color:#960 }
+
+          .hl.num { color:#f30 } /* number */
+          .hl.esc { color:#f0f } /* escape character */
+          .hl.str { color:#f30 } /* string */
+          .hl.ppc { color:#c3c } /* preprocessor */
+          .hl.pps { color:#f30 } /* preprocessor string */
+          .hl.slc { color:#099 } /* single-line comment */
+          .hl.com { color:#099 } /* multi-line comment */
+          /* .hl.opt { color:#ccc } */ /* operator */
+          /* .hl.ipl { color:#ccc } */ /* interpolation */
+
+          /* keyword groups kw[a-z] */
+          .hl.kwa { color:#f90 }
+          .hl.kwb { color:#060 }
+          .hl.kwc { color:#f90 }
+          /* .hl.kwd { color:#ccc } */
+        '';
+      };
+    };
+
+    services.postfix = {
+      enable = true;
+      setSendmail = true;
+      #sslCert = "${tls-cert}/cert.pem";
+      #sslKey = "${tls-cert}/key.pem";
+      recipientDelimiter = "+";
+    };
+
+    environment.systemPackages = [
+      pkgs.mailutils
+      pkgs.openssl
+    ];
+
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("public-inbox-init.service")
+
+    # Very basic check that Gitolite can work;
+    # Gitolite is not needed for the rest of this testScript
+    machine.wait_for_unit("gitolite-init.service")
+
+    # List inboxes through public-inbox-httpd
+    machine.wait_for_unit("nginx.service")
+    machine.succeed("curl -L https://machine.${domain} | grep repo1@${domain}")
+    # The repo2 inbox is hidden
+    machine.fail("curl -L https://machine.${domain} | grep repo2@${domain}")
+    machine.wait_for_unit("public-inbox-httpd.service")
+
+    # Send a mail and read it through public-inbox-httpd
+    # Must work too when using a recipientDelimiter.
+    machine.wait_for_unit("postfix.service")
+    machine.succeed("mail -t <${pkgs.writeText "mail" ''
+      Subject: Testing mail
+      From: root@localhost
+      To: repo1+extension@${domain}
+      Message-ID: <repo1@root-1>
+      Content-Type: text/plain; charset=utf-8
+      Content-Disposition: inline
+
+      This is a testing mail.
+    ''}")
+    machine.sleep(10)
+    machine.succeed("curl -L 'https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u' | grep 'This is a testing mail.'")
+
+    # Read a mail through public-inbox-imapd
+    machine.wait_for_open_port(993)
+    machine.wait_for_unit("public-inbox-imapd.service")
+    machine.succeed("openssl s_client -ign_eof -crlf -connect machine.${domain}:993 <${pkgs.writeText "imap-commands" ''
+      tag login anonymous@${domain} anonymous
+      tag SELECT INBOX.comp.${orga}.repo1.0
+      tag FETCH 1 (BODY[HEADER])
+      tag LOGOUT
+    ''} | grep '^Message-ID: <repo1@root-1>'")
+
+    # TODO: Read a mail through public-inbox-nntpd
+    #machine.wait_for_open_port(563)
+    #machine.wait_for_unit("public-inbox-nntpd.service")
+
+    # Delete a mail.
+    # Note that the use of an extension not listed in the addresses
+    # require to use --all
+    machine.succeed("curl -L https://machine.${domain}/inbox/repo1/repo1@root-1/raw | sudo -u public-inbox public-inbox-learn rm --all")
+    machine.fail("curl -L https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u | grep 'This is a testing mail.'")
+
+    # Compact the database
+    machine.succeed("sudo -u public-inbox public-inbox-compact --all")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pufferpanel.nix b/nixpkgs/nixos/tests/pufferpanel.nix
new file mode 100644
index 000000000000..e7b09c13f90b
--- /dev/null
+++ b/nixpkgs/nixos/tests/pufferpanel.nix
@@ -0,0 +1,74 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "pufferpanel";
+  meta.maintainers = [ lib.maintainers.tie ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.pufferpanel ];
+    services.pufferpanel = {
+      enable = true;
+      extraPackages = [ pkgs.netcat ];
+      environment = {
+        PUFFER_PANEL_REGISTRATIONENABLED = "false";
+        PUFFER_PANEL_SETTINGS_COMPANYNAME = "NixOS";
+      };
+    };
+  };
+
+  testScript = ''
+    import shlex
+    import json
+
+    curl = "curl --fail-with-body --silent"
+    baseURL = "http://localhost:8080"
+    adminName = "admin"
+    adminEmail = "admin@nixos.org"
+    adminPass = "admin"
+    adminCreds = json.dumps({
+      "email": adminEmail,
+      "password": adminPass,
+    })
+    stopCode = 9 # SIGKILL
+    serverPort = 1337
+    serverDefinition = json.dumps({
+      "name": "netcat",
+      "node": 0,
+      "users": [
+        adminName,
+      ],
+      "type": "netcat",
+      "run": {
+        "stopCode": stopCode,
+        "command": f"nc -l {serverPort}",
+      },
+      "environment": {
+        "type": "standard",
+      },
+    })
+
+    start_all()
+
+    machine.wait_for_unit("pufferpanel.service")
+    machine.wait_for_open_port(5657) # SFTP
+    machine.wait_for_open_port(8080) # HTTP
+
+    # Note that PufferPanel does not initialize database unless necessary.
+    # /api/config endpoint creates database file and triggers migrations.
+    # On success, we run a command to create administrator user that we use to
+    # interact with HTTP API.
+    resp = json.loads(machine.succeed(f"{curl} {baseURL}/api/config"))
+    assert resp["branding"]["name"] == "NixOS", "Invalid company name in configuration"
+    assert resp["registrationEnabled"] == False, "Expected registration to be disabled"
+
+    machine.succeed(f"pufferpanel --workDir /var/lib/pufferpanel user add --admin --name {adminName} --email {adminEmail} --password {adminPass}")
+
+    resp = json.loads(machine.succeed(f"{curl} -d '{adminCreds}' {baseURL}/auth/login"))
+    assert "servers.admin" in resp["scopes"], "User is not administrator"
+    token = resp["session"]
+    authHeader = shlex.quote(f"Authorization: Bearer {token}")
+
+    resp = json.loads(machine.succeed(f"{curl} -H {authHeader} -H 'Content-Type: application/json' -d '{serverDefinition}' {baseURL}/api/servers"))
+    serverID = resp["id"]
+    machine.succeed(f"{curl} -X POST -H {authHeader} {baseURL}/proxy/daemon/server/{serverID}/start")
+    machine.wait_for_open_port(serverPort)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/pulseaudio.nix b/nixpkgs/nixos/tests/pulseaudio.nix
new file mode 100644
index 000000000000..dc8e33ccd559
--- /dev/null
+++ b/nixpkgs/nixos/tests/pulseaudio.nix
@@ -0,0 +1,80 @@
+let
+  mkTest = { systemWide ? false , fullVersion ? false }:
+    import ./make-test-python.nix ({ pkgs, lib, ... }:
+      let
+        testFile = pkgs.fetchurl {
+          url =
+            "https://file-examples.com/storage/fe5947fd2362fc197a3c2df/2017/11/file_example_MP3_700KB.mp3";
+          hash = "sha256-+iggJW8s0/LfA/okfXsB550/55Q0Sq3OoIzuBrzOPJQ=";
+        };
+
+        makeTestPlay = key:
+          { sox, alsa-utils }:
+          pkgs.writeScriptBin key ''
+            set -euxo pipefail
+            ${sox}/bin/play ${testFile}
+            ${sox}/bin/sox ${testFile} -t wav - | ${alsa-utils}/bin/aplay
+            touch /tmp/${key}_success
+          '';
+
+        testers = builtins.mapAttrs makeTestPlay {
+          testPlay = { inherit (pkgs) sox alsa-utils; };
+          testPlay32 = { inherit (pkgs.pkgsi686Linux) sox alsa-utils; };
+        };
+      in {
+        name = "pulseaudio${lib.optionalString fullVersion "Full"}${lib.optionalString systemWide "-systemWide"}";
+        meta = with pkgs.lib.maintainers; {
+          maintainers = [ synthetica ] ++ pkgs.pulseaudio.meta.maintainers;
+        };
+
+        nodes.machine = { ... }:
+
+          {
+            imports = [ ./common/wayland-cage.nix ];
+            hardware.pulseaudio = {
+              enable = true;
+              support32Bit = true;
+              inherit systemWide;
+            } // lib.optionalAttrs fullVersion {
+              package = pkgs.pulseaudioFull;
+            };
+
+            environment.systemPackages = [ testers.testPlay pkgs.pavucontrol ]
+              ++ lib.optional pkgs.stdenv.isx86_64 testers.testPlay32;
+          } // lib.optionalAttrs systemWide {
+            users.users.alice.extraGroups = [ "pulse-access" ];
+            systemd.services.pulseaudio.wantedBy = [ "multi-user.target" ];
+          };
+
+        enableOCR = true;
+
+        testScript = { ... }: ''
+          machine.wait_until_succeeds("pgrep xterm")
+          machine.wait_for_text("alice@machine")
+
+          machine.send_chars("testPlay \n")
+          machine.wait_for_file("/tmp/testPlay_success")
+          ${lib.optionalString pkgs.stdenv.isx86_64 ''
+            machine.send_chars("testPlay32 \n")
+            machine.wait_for_file("/tmp/testPlay32_success")
+          ''}
+          machine.screenshot("testPlay")
+
+          ${lib.optionalString (!systemWide) ''
+            machine.send_chars("pacmd info && touch /tmp/pacmd_success\n")
+            machine.wait_for_file("/tmp/pacmd_success")
+          ''}
+
+          # Pavucontrol only loads when Pulseaudio is running. If it isn't, the
+          # text "Dummy Output" (sound device name) will never show.
+          machine.send_chars("pavucontrol\n")
+          machine.wait_for_text("Dummy Output")
+          machine.screenshot("Pavucontrol")
+        '';
+      });
+in builtins.mapAttrs (key: val: mkTest val) {
+  user = { systemWide = false; fullVersion = false; };
+  system = { systemWide = true; fullVersion = false; };
+  userFull = { systemWide = false; fullVersion = true; };
+  systemFull = { systemWide = true; fullVersion = true; };
+}
diff --git a/nixpkgs/nixos/tests/pykms.nix b/nixpkgs/nixos/tests/pykms.nix
new file mode 100644
index 000000000000..14d776a2f113
--- /dev/null
+++ b/nixpkgs/nixos/tests/pykms.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  {
+    name = "pykms-test";
+    meta.maintainers = with pkgs.lib.maintainers; [ zopieux ];
+
+    nodes.machine = { config, lib, pkgs, ... }: {
+      services.pykms.enable = true;
+    };
+
+    testScript = ''
+      machine.wait_for_unit("pykms.service")
+      machine.succeed("${pkgs.pykms}/bin/client")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/pyload.nix b/nixpkgs/nixos/tests/pyload.nix
new file mode 100644
index 000000000000..f913e822b2ff
--- /dev/null
+++ b/nixpkgs/nixos/tests/pyload.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "pyload";
+  meta.maintainers = with lib.maintainers; [ ambroisie ];
+
+  nodes = {
+    machine = { ... }: {
+      services.pyload = {
+        enable = true;
+
+        listenAddress = "0.0.0.0";
+        port = 9876;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 9876 ];
+    };
+
+    client = { };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("pyload.service")
+
+    with subtest("Web interface accessible locally"):
+        machine.wait_until_succeeds("curl -fs localhost:9876")
+
+    client.wait_for_unit("network.target")
+
+    with subtest("Web interface accessible from a different machine"):
+        client.wait_until_succeeds("curl -fs machine:9876")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/qboot.nix b/nixpkgs/nixos/tests/qboot.nix
new file mode 100644
index 000000000000..29d999be58e5
--- /dev/null
+++ b/nixpkgs/nixos/tests/qboot.nix
@@ -0,0 +1,13 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "qboot";
+
+  nodes.machine = { ... }: {
+    virtualisation.bios = pkgs.qboot;
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/qemu-vm-external-disk-image.nix b/nixpkgs/nixos/tests/qemu-vm-external-disk-image.nix
new file mode 100644
index 000000000000..a229fc5e3963
--- /dev/null
+++ b/nixpkgs/nixos/tests/qemu-vm-external-disk-image.nix
@@ -0,0 +1,73 @@
+# Tests that you can boot from an external disk image with the qemu-vm module.
+# "External" here means that the image was not produced within the qemu-vm
+# module and relies on the fileSystems option also set outside the qemu-vm
+# module. Most notably, this tests that you can stop the qemu-vm module from
+# overriding fileSystems with virtualisation.fileSystems so you don't have to
+# replicate the previously set fileSystems in virtualisation.fileSystems.
+
+{ lib, ... }:
+
+let
+  rootFslabel = "external";
+  rootFsDevice = "/dev/disk/by-label/${rootFslabel}";
+
+  externalModule = { config, lib, pkgs, ... }: {
+    boot.loader.systemd-boot.enable = true;
+
+    fileSystems = {
+      "/".device = rootFsDevice;
+    };
+
+    system.build.diskImage = import ../lib/make-disk-image.nix {
+      inherit config lib pkgs;
+      label = rootFslabel;
+      partitionTableType = "efi";
+      format = "qcow2";
+      bootSize = "32M";
+      additionalSpace = "0M";
+      copyChannel = false;
+    };
+  };
+in
+{
+  name = "qemu-vm-external-disk-image";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    virtualisation.directBoot.enable = false;
+    virtualisation.mountHostNixStore = false;
+    virtualisation.useEFIBoot = true;
+
+    # This stops the qemu-vm module from overriding the fileSystems option
+    # with virtualisation.fileSystems.
+    virtualisation.fileSystems = lib.mkForce { };
+
+    imports = [ externalModule ];
+  };
+
+  testScript = { nodes, ... }: ''
+    import os
+    import subprocess
+    import tempfile
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    subprocess.run([
+      "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img",
+      "create",
+      "-f",
+      "qcow2",
+      "-b",
+      "${nodes.machine.system.build.diskImage}/nixos.qcow2",
+      "-F",
+      "qcow2",
+      tmp_disk_image.name,
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+
+    machine.succeed("findmnt --kernel --source ${rootFsDevice} --target /")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/qemu-vm-restrictnetwork.nix b/nixpkgs/nixos/tests/qemu-vm-restrictnetwork.nix
new file mode 100644
index 000000000000..49aefcc099bd
--- /dev/null
+++ b/nixpkgs/nixos/tests/qemu-vm-restrictnetwork.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({
+  name = "qemu-vm-restrictnetwork";
+
+  nodes = {
+    unrestricted = { config, pkgs, ... }: {
+      virtualisation.restrictNetwork = false;
+    };
+
+    restricted = { config, pkgs, ... }: {
+      virtualisation.restrictNetwork = true;
+    };
+  };
+
+  testScript = ''
+    import os
+
+    if os.fork() == 0:
+      # Start some HTTP server on the qemu host to test guest isolation.
+      from http.server import HTTPServer, BaseHTTPRequestHandler
+      HTTPServer(("", 8000), BaseHTTPRequestHandler).serve_forever()
+
+    else:
+      start_all()
+      unrestricted.systemctl("start network-online.target")
+      restricted.systemctl("start network-online.target")
+      unrestricted.wait_for_unit("network-online.target")
+      restricted.wait_for_unit("network-online.target")
+
+      # Guests should be able to reach each other on the same VLAN.
+      unrestricted.succeed("ping -c1 restricted")
+      restricted.succeed("ping -c1 unrestricted")
+
+      # Only the unrestricted guest should be able to reach host services.
+      # 10.0.2.2 is the gateway mapping to the host's loopback interface.
+      unrestricted.succeed("curl -s http://10.0.2.2:8000")
+      restricted.fail("curl -s http://10.0.2.2:8000")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/qemu-vm-volatile-root.nix b/nixpkgs/nixos/tests/qemu-vm-volatile-root.nix
new file mode 100644
index 000000000000..bc8fd853409d
--- /dev/null
+++ b/nixpkgs/nixos/tests/qemu-vm-volatile-root.nix
@@ -0,0 +1,17 @@
+# Test that the root filesystem is a volatile tmpfs.
+
+{ lib, ... }:
+
+{
+  name = "qemu-vm-volatile-root";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = _: {
+    virtualisation.diskImage = null;
+  };
+
+  testScript = ''
+    machine.succeed("findmnt --kernel --types tmpfs /")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/qgis.nix b/nixpkgs/nixos/tests/qgis.nix
new file mode 100644
index 000000000000..7706b8c07747
--- /dev/null
+++ b/nixpkgs/nixos/tests/qgis.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, lib, qgisPackage, ... }:
+  let
+    testScript = pkgs.writeTextFile {
+      name = "qgis-test.py";
+      text = (builtins.readFile ../../pkgs/applications/gis/qgis/test.py);
+    };
+  in
+  {
+    name = "qgis";
+    meta = {
+      maintainers = with lib; [ teams.geospatial.members ];
+    };
+
+    nodes = {
+      machine = { pkgs, ... }: {
+        virtualisation.diskSize = 2 * 1024;
+
+        imports = [ ./common/x11.nix ];
+        environment.systemPackages = [ qgisPackage ];
+
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      machine.succeed("${qgisPackage}/bin/qgis --version | grep 'QGIS ${qgisPackage.version}'")
+      machine.succeed("${qgisPackage}/bin/qgis --code ${testScript}")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/qownnotes.nix b/nixpkgs/nixos/tests/qownnotes.nix
new file mode 100644
index 000000000000..3390ba6d9025
--- /dev/null
+++ b/nixpkgs/nixos/tests/qownnotes.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ lib, pkgs, ...} :
+
+{
+  name = "qownnotes";
+  meta.maintainers = [ lib.maintainers.pbek ];
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [
+      ./common/user-account.nix
+      ./common/x11.nix
+    ];
+
+    test-support.displayManager.auto.user = "alice";
+    environment.systemPackages = [
+      pkgs.qownnotes
+      pkgs.xdotool
+    ];
+  };
+
+  enableOCR = true;
+
+  # https://nixos.org/manual/nixos/stable/#ssec-machine-objects
+  testScript = { nodes, ... }: let
+    aliceDo = cmd: ''machine.succeed("su - alice -c '${cmd}' >&2 &");'';
+    in ''
+    with subtest("Ensure X starts"):
+        start_all()
+        machine.wait_for_x()
+
+    with subtest("Check QOwnNotes version on CLI"):
+        ${aliceDo "qownnotes --version"}
+
+        machine.wait_for_console_text("QOwnNotes ${pkgs.qownnotes.version}")
+
+    with subtest("Ensure QOwnNotes starts"):
+        # start QOwnNotes window
+        ${aliceDo "qownnotes"}
+
+        machine.wait_for_text("Welcome to QOwnNotes")
+        machine.screenshot("QOwnNotes-Welcome")
+
+    with subtest("Finish first-run wizard"):
+        # The wizard should show up now
+        machine.wait_for_text("Note folder")
+        machine.send_key("ret")
+        machine.wait_for_console_text("Note path '/home/alice/Notes' was now created.")
+        machine.wait_for_text("Panel layout")
+        machine.send_key("ret")
+        machine.wait_for_text("Nextcloud")
+        machine.send_key("ret")
+        machine.wait_for_text("App metric")
+        machine.send_key("ret")
+
+        # Doesn't work for non-root
+        #machine.wait_for_window("QOwnNotes - ${pkgs.qownnotes.version}")
+
+        # OCR doesn't seem to be able any more to handle the main window
+        #machine.wait_for_text("QOwnNotes - ${pkgs.qownnotes.version}")
+
+        # The main window should now show up
+        machine.wait_for_open_port(22222)
+        machine.wait_for_console_text("QOwnNotes server listening on port 22222")
+
+        machine.screenshot("QOwnNotes-DemoNote")
+
+    with subtest("Create a new note"):
+        machine.send_key("ctrl-n")
+        machine.sleep(1)
+        machine.send_chars("This is a NixOS test!\n")
+        machine.wait_until_succeeds("find /home/alice/Notes -type f | grep -qi 'Note 2'")
+
+        # OCR doesn't seem to be able any more to handle the main window
+        #machine.wait_for_text("This is a NixOS test!")
+
+        # Doesn't work for non-root
+        #machine.wait_for_window("- QOwnNotes - ${pkgs.qownnotes.version}")
+
+        machine.screenshot("QOwnNotes-NewNote")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/quake3.nix b/nixpkgs/nixos/tests/quake3.nix
new file mode 100644
index 000000000000..2d8c5207001c
--- /dev/null
+++ b/nixpkgs/nixos/tests/quake3.nix
@@ -0,0 +1,95 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+let
+
+  # Build Quake with coverage instrumentation.
+  overrides = pkgs:
+    {
+      quake3game = pkgs.quake3game.override (args: {
+        stdenv = pkgs.stdenvAdapters.addCoverageInstrumentation args.stdenv;
+      });
+    };
+
+  # Only allow the demo data to be used (only if it's unfreeRedistributable).
+  unfreePredicate = pkg: let
+    allowPackageNames = [ "quake3-demodata" "quake3-pointrelease" ];
+    allowLicenses = [ lib.licenses.unfreeRedistributable ];
+  in lib.elem pkg.pname allowPackageNames &&
+     lib.elem (pkg.meta.license or null) allowLicenses;
+
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      hardware.opengl.driSupport = true;
+      environment.systemPackages = [ pkgs.quake3demo ];
+      nixpkgs.config.packageOverrides = overrides;
+      nixpkgs.config.allowUnfreePredicate = unfreePredicate;
+    };
+
+in
+
+rec {
+  name = "quake3";
+  meta = with lib.maintainers; {
+    maintainers = [ domenkozar eelco ];
+  };
+
+  # TODO: lcov doesn't work atm
+  #makeCoverageReport = true;
+
+  nodes =
+    { server =
+        { pkgs, ... }:
+
+        { systemd.services.quake3-server =
+            { wantedBy = [ "multi-user.target" ];
+              script =
+                "${pkgs.quake3demo}/bin/quake3-server +set g_gametype 0 " +
+                "+map q3dm7 +addbot grunt +addbot daemia 2> /tmp/log";
+            };
+          nixpkgs.config.packageOverrides = overrides;
+          nixpkgs.config.allowUnfreePredicate = unfreePredicate;
+          networking.firewall.allowedUDPPorts = [ 27960 ];
+        };
+
+      client1 = client;
+      client2 = client;
+    };
+
+  testScript =
+    ''
+      start_all()
+
+      server.wait_for_unit("quake3-server")
+      client1.wait_for_x()
+      client2.wait_for_x()
+
+      client1.execute("quake3 +set r_fullscreen 0 +set name Foo +connect server &")
+      client2.execute("quake3 +set r_fullscreen 0 +set name Bar +connect server &")
+
+      server.wait_until_succeeds("grep -q 'Foo.*entered the game' /tmp/log")
+      server.wait_until_succeeds("grep -q 'Bar.*entered the game' /tmp/log")
+
+      server.sleep(10)  # wait for a while to get a nice screenshot
+
+      client1.block()
+
+      server.sleep(20)
+
+      client1.screenshot("screen1")
+      client2.screenshot("screen2")
+
+      client1.unblock()
+
+      server.sleep(10)
+
+      client1.screenshot("screen3")
+      client2.screenshot("screen4")
+
+      client1.shutdown()
+      client2.shutdown()
+      server.stop_job("quake3-server")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/quicktun.nix b/nixpkgs/nixos/tests/quicktun.nix
new file mode 100644
index 000000000000..a5a632457117
--- /dev/null
+++ b/nixpkgs/nixos/tests/quicktun.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "quicktun";
+  meta.maintainers = with lib.maintainers; [ h7x4 ];
+
+  nodes = {
+    machine = { ... }: {
+      services.quicktun."test-tunnel" = {
+        protocol = "raw";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("quicktun-test-tunnel.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/quorum.nix b/nixpkgs/nixos/tests/quorum.nix
new file mode 100644
index 000000000000..31669eb7fc38
--- /dev/null
+++ b/nixpkgs/nixos/tests/quorum.nix
@@ -0,0 +1,102 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  keystore =  {
+    address = "9377bc3936de934c497e22917b81aa8774ac3bb0";
+    crypto = {
+      cipher = "aes-128-ctr";
+      ciphertext = "ad8341d8ef225650403fd366c955f41095e438dd966a3c84b3d406818c1e366c";
+      cipherparams = {
+        iv = "2a09f7a72fd6dff7c43150ff437e6ac2";
+      };
+      kdf = "scrypt";
+      kdfparams = {
+        dklen = 32;
+        n = 262144;
+        p = 1;
+        r = 8;
+        salt = "d1a153845bb80cd6274c87c5bac8ac09fdfac5ff131a6f41b5ed319667f12027";
+      };
+      mac = "a9621ad88fa1d042acca6fc2fcd711f7e05bfbadea3f30f379235570c8e270d3";
+    };
+    id = "89e847a3-1527-42f6-a321-77de0a14ce02";
+    version = 3;
+  };
+  keystore-file = pkgs.writeText "keystore-file" (builtins.toJSON keystore);
+in
+{
+  name = "quorum";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.quorum = {
+        enable = true;
+        permissioned = false;
+        staticNodes = [ "enode://dd333ec28f0a8910c92eb4d336461eea1c20803eed9cf2c056557f986e720f8e693605bba2f4e8f289b1162e5ac7c80c914c7178130711e393ca76abc1d92f57@0.0.0.0:30303?discport=0" ];
+        genesis = {
+          alloc = {
+            "189d23d201b03ae1cf9113672df29a5d672aefa3" = {
+              balance = "0x446c3b15f9926687d2c40534fdb564000000000000";
+            };
+            "44b07d2c28b8ed8f02b45bd84ac7d9051b3349e6" = {
+              balance = "0x446c3b15f9926687d2c40534fdb564000000000000";
+            };
+            "4c1ccd426833b9782729a212c857f2f03b7b4c0d" = {
+              balance = "0x446c3b15f9926687d2c40534fdb564000000000000";
+            };
+            "7ae555d0f6faad7930434abdaac2274fd86ab516" = {
+              balance = "0x446c3b15f9926687d2c40534fdb564000000000000";
+            };
+            c1056df7c02b6f1a353052eaf0533cc7cb743b52 = {
+              balance = "0x446c3b15f9926687d2c40534fdb564000000000000";
+            };
+          };
+          coinbase = "0x0000000000000000000000000000000000000000";
+          config = {
+            byzantiumBlock = 1;
+            chainId = 10;
+            eip150Block = 1;
+            eip150Hash =
+              "0x0000000000000000000000000000000000000000000000000000000000000000";
+            eip155Block = 1;
+            eip158Block = 1;
+            isQuorum = true;
+            istanbul = {
+              epoch = 30000;
+              policy = 0;
+            };
+          };
+        difficulty = "0x1";
+        extraData =
+          "0x0000000000000000000000000000000000000000000000000000000000000000f8aff869944c1ccd426833b9782729a212c857f2f03b7b4c0d94189d23d201b03ae1cf9113672df29a5d672aefa39444b07d2c28b8ed8f02b45bd84ac7d9051b3349e694c1056df7c02b6f1a353052eaf0533cc7cb743b52947ae555d0f6faad7930434abdaac2274fd86ab516b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0";
+        gasLimit = "0xe0000000";
+        gasUsed = "0x0";
+        mixHash =
+          "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365";
+        nonce = "0x0";
+        number = "0x0";
+        parentHash =
+          "0x0000000000000000000000000000000000000000000000000000000000000000";
+        timestamp = "0x5cffc201";
+      };
+     };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.succeed("mkdir -p /var/lib/quorum/keystore")
+    machine.succeed(
+        'cp ${keystore-file} /var/lib/quorum/keystore/UTC--2020-03-23T11-08-34.144812212Z--${keystore.address}'
+    )
+    machine.succeed(
+        "echo fe2725c4e8f7617764b845e8d939a65c664e7956eb47ed7d934573f16488efc1 > /var/lib/quorum/nodekey"
+    )
+    machine.succeed("systemctl restart quorum")
+    machine.wait_for_unit("quorum.service")
+    machine.sleep(15)
+    machine.succeed('geth attach /var/lib/quorum/geth.ipc --exec "eth.accounts" | grep ${keystore.address}')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/rabbitmq.nix b/nixpkgs/nixos/tests/rabbitmq.nix
new file mode 100644
index 000000000000..040679e68d98
--- /dev/null
+++ b/nixpkgs/nixos/tests/rabbitmq.nix
@@ -0,0 +1,61 @@
+# This test runs rabbitmq and checks if rabbitmq is up and running.
+
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  # in real life, you would keep this out of your repo and deploy it to a safe
+  # location using safe means.
+  configKeyPath = pkgs.writeText "fake-config-key" "hOjWzSEn2Z7cHzKOcf6i183O2NdjurSuoMDIIv01";
+in
+{
+  name = "rabbitmq";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco offline ];
+  };
+
+  nodes.machine = {
+    services.rabbitmq = {
+      enable = true;
+      managementPlugin.enable = true;
+
+      # To encrypt:
+      # rabbitmqctl --quiet encode --cipher blowfish_cfb64 --hash sha256 \
+      #   --iterations 10000 '<<"dJT8isYu6t0Xb6u56rPglSj1vK51SlNVlXfwsRxw">>' \
+      #   "hOjWzSEn2Z7cHzKOcf6i183O2NdjurSuoMDIIv01" ;
+      config = ''
+        [ { rabbit
+          , [ {default_user, <<"alice">>}
+            , { default_pass
+              , {encrypted,<<"oKKxyTze9PYmsEfl6FG1MxIUhxY7WPQL7HBoMPRC/1ZOdOZbtr9+DxjWW3e1D5SL48n3D9QOsGD0cOgYG7Qdvb7Txrepw8w=">>}
+              }
+            , {config_entry_decoder
+              , [ {passphrase, {file, <<"${configKeyPath}">>}}
+                , {cipher, blowfish_cfb64}
+                , {hash, sha256}
+                , {iterations, 10000}
+                ]
+              }
+            % , {rabbitmq_management, [{path_prefix, "/_queues"}]}
+            ]
+          }
+        ].
+      '';
+    };
+    # Ensure there is sufficient extra disk space for rabbitmq to be happy
+    virtualisation.diskSize = 1024;
+  };
+
+  testScript = ''
+    machine.start()
+
+    machine.wait_for_unit("rabbitmq.service")
+    machine.wait_until_succeeds(
+        'su -s ${pkgs.runtimeShell} rabbitmq -c "rabbitmqctl status"'
+    )
+    machine.wait_for_open_port(15672)
+
+    # The password is the plaintext that was encrypted with rabbitmqctl encode above.
+    machine.wait_until_succeeds(
+        '${pkgs.rabbitmq-java-client}/bin/PerfTest --time 10 --uri amqp://alice:dJT8isYu6t0Xb6u56rPglSj1vK51SlNVlXfwsRxw@localhost'
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/radarr.nix b/nixpkgs/nixos/tests/radarr.nix
new file mode 100644
index 000000000000..bf9eb11c2b12
--- /dev/null
+++ b/nixpkgs/nixos/tests/radarr.nix
@@ -0,0 +1,16 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "radarr";
+  meta.maintainers = with lib.maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.radarr.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("radarr.service")
+    machine.wait_for_open_port(7878)
+    machine.succeed("curl --fail http://localhost:7878/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/radicale.nix b/nixpkgs/nixos/tests/radicale.nix
new file mode 100644
index 000000000000..66650dce4a00
--- /dev/null
+++ b/nixpkgs/nixos/tests/radicale.nix
@@ -0,0 +1,95 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+let
+  user = "someuser";
+  password = "some_password";
+  port = "5232";
+  filesystem_folder = "/data/radicale";
+
+  cli = "${pkgs.calendar-cli}/bin/calendar-cli --caldav-user ${user} --caldav-pass ${password}";
+in {
+  name = "radicale3";
+  meta.maintainers = with lib.maintainers; [ dotlambda ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.radicale = {
+      enable = true;
+      settings = {
+        auth = {
+          type = "htpasswd";
+          htpasswd_filename = "/etc/radicale/users";
+          htpasswd_encryption = "bcrypt";
+        };
+        storage = {
+          inherit filesystem_folder;
+          hook = "git add -A && (git diff --cached --quiet || git commit -m 'Changes by '%(user)s)";
+        };
+        logging.level = "info";
+      };
+      rights = {
+        principal = {
+          user = ".+";
+          collection = "{user}";
+          permissions = "RW";
+        };
+        calendars = {
+          user = ".+";
+          collection = "{user}/[^/]+";
+          permissions = "rw";
+        };
+      };
+    };
+    systemd.services.radicale.path = [ pkgs.git ];
+    environment.systemPackages = [ pkgs.git ];
+    systemd.tmpfiles.rules = [ "d ${filesystem_folder} 0750 radicale radicale -" ];
+    # WARNING: DON'T DO THIS IN PRODUCTION!
+    # This puts unhashed secrets directly into the Nix store for ease of testing.
+    environment.etc."radicale/users".source = pkgs.runCommand "htpasswd" {} ''
+      ${pkgs.apacheHttpd}/bin/htpasswd -bcB "$out" ${user} ${password}
+    '';
+  };
+  testScript = ''
+    machine.wait_for_unit("radicale.service")
+    machine.wait_for_open_port(${port})
+
+    machine.succeed("sudo -u radicale git -C ${filesystem_folder} init")
+    machine.succeed(
+        "sudo -u radicale git -C ${filesystem_folder} config --local user.email radicale@example.com"
+    )
+    machine.succeed(
+        "sudo -u radicale git -C ${filesystem_folder} config --local user.name radicale"
+    )
+
+    with subtest("Test calendar and event creation"):
+        machine.succeed(
+            "${cli} --caldav-url http://localhost:${port}/${user} calendar create cal"
+        )
+        machine.succeed("test -d ${filesystem_folder}/collection-root/${user}/cal")
+        machine.succeed('test -z "$(ls ${filesystem_folder}/collection-root/${user}/cal)"')
+        machine.succeed(
+            "${cli} --caldav-url http://localhost:${port}/${user}/cal calendar add 2021-04-23 testevent"
+        )
+        machine.succeed('test -n "$(ls ${filesystem_folder}/collection-root/${user}/cal)"')
+        (status, stdout) = machine.execute(
+            "sudo -u radicale git -C ${filesystem_folder} log --format=oneline | wc -l"
+        )
+        assert status == 0, "git log failed"
+        assert stdout == "3\n", "there should be exactly 3 commits"
+
+    with subtest("Test rights file"):
+        machine.fail(
+            "${cli} --caldav-url http://localhost:${port}/${user} calendar create sub/cal"
+        )
+        machine.fail(
+            "${cli} --caldav-url http://localhost:${port}/otheruser calendar create cal"
+        )
+
+    with subtest("Test web interface"):
+        machine.succeed("curl --fail http://${user}:${password}@localhost:${port}/.web/")
+
+    with subtest("Test security"):
+        output = machine.succeed("systemd-analyze security radicale.service")
+        machine.log(output)
+        assert output[-9:-1] == "SAFE :-}"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ragnarwm.nix b/nixpkgs/nixos/tests/ragnarwm.nix
new file mode 100644
index 000000000000..f7c588b92008
--- /dev/null
+++ b/nixpkgs/nixos/tests/ragnarwm.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ lib, ...} : {
+  name = "ragnarwm";
+
+  meta = {
+    maintainers = with lib.maintainers; [ sigmanificient ];
+  };
+
+  nodes.machine = { pkgs, lib, ... }: {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    test-support.displayManager.auto.user = "alice";
+    services.xserver.displayManager.defaultSession = lib.mkForce "ragnar";
+    services.xserver.windowManager.ragnarwm.enable = true;
+
+    # Setup the default terminal of Ragnar
+    environment.systemPackages = [ pkgs.alacritty ];
+  };
+
+  testScript = ''
+    with subtest("ensure x starts"):
+        machine.wait_for_x()
+        machine.wait_for_file("/home/alice/.Xauthority")
+        machine.succeed("xauth merge ~alice/.Xauthority")
+
+    with subtest("ensure we can open a new terminal"):
+        # Sleeping a bit before the test, as it may help for sending keys
+        machine.sleep(2)
+        machine.send_key("meta_l-ret")
+        machine.wait_for_window(r"alice.*?machine")
+        machine.sleep(2)
+        machine.screenshot("terminal")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/rasdaemon.nix b/nixpkgs/nixos/tests/rasdaemon.nix
new file mode 100644
index 000000000000..7f30a3b81ab5
--- /dev/null
+++ b/nixpkgs/nixos/tests/rasdaemon.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, ... } : {
+  name = "rasdaemon";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ evils ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    hardware.rasdaemon = {
+      enable = true;
+      # should be enabled by default, just making sure
+      record = true;
+      # nonsense label
+      labels = ''
+        vendor: none
+          product: none
+          model: none
+            DIMM_0: 0.0.0;
+      '';
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      # confirm rasdaemon is running and has a valid database
+      # some disk errors detected in qemu for some reason ¯\_(ツ)_/¯
+      machine.succeed("ras-mc-ctl --errors | tee /dev/stderr | grep -q 'No .* errors.'")
+      # confirm the supplied labels text made it into the system
+      machine.succeed("grep -q 'vendor: none' /etc/ras/dimm_labels.d/labels >&2")
+      machine.shutdown()
+    '';
+})
diff --git a/nixpkgs/nixos/tests/readarr.nix b/nixpkgs/nixos/tests/readarr.nix
new file mode 100644
index 000000000000..7c144e2ee02f
--- /dev/null
+++ b/nixpkgs/nixos/tests/readarr.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "readarr";
+  meta.maintainers = with lib.maintainers; [ jocelynthode ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.readarr.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("readarr.service")
+    machine.wait_for_open_port(8787)
+    machine.succeed("curl --fail http://localhost:8787/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/redis.nix b/nixpkgs/nixos/tests/redis.nix
new file mode 100644
index 000000000000..94b50d07be6d
--- /dev/null
+++ b/nixpkgs/nixos/tests/redis.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "redis";
+  meta.maintainers = with lib.maintainers; [ flokli ];
+
+  nodes = {
+    machine =
+      { pkgs, lib, ... }:
+
+      {
+        services.redis.servers."".enable = true;
+        services.redis.servers."test".enable = true;
+
+        users.users = lib.listToAttrs (map (suffix: lib.nameValuePair "member${suffix}" {
+          createHome = false;
+          description = "A member of the redis${suffix} group";
+          isNormalUser = true;
+          extraGroups = [ "redis${suffix}" ];
+        }) ["" "-test"]);
+      };
+  };
+
+  testScript = { nodes, ... }: let
+    inherit (nodes.machine.config.services) redis;
+    in ''
+    start_all()
+    machine.wait_for_unit("redis")
+    machine.wait_for_unit("redis-test")
+
+    # The unnamed Redis server still opens a port for backward-compatibility
+    machine.wait_for_open_port(6379)
+
+    machine.wait_for_file("${redis.servers."".unixSocket}")
+    machine.wait_for_file("${redis.servers."test".unixSocket}")
+
+    # The unix socket is accessible to the redis group
+    machine.succeed('su member -c "redis-cli ping | grep PONG"')
+    machine.succeed('su member-test -c "redis-cli ping | grep PONG"')
+
+    machine.succeed("redis-cli ping | grep PONG")
+    machine.succeed("redis-cli -s ${redis.servers."".unixSocket} ping | grep PONG")
+    machine.succeed("redis-cli -s ${redis.servers."test".unixSocket} ping | grep PONG")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/redmine.nix b/nixpkgs/nixos/tests/redmine.nix
new file mode 100644
index 000000000000..621b3e6a36ee
--- /dev/null
+++ b/nixpkgs/nixos/tests/redmine.nix
@@ -0,0 +1,44 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  redmineTest = { name, type }: makeTest {
+    name = "redmine-${name}";
+    nodes.machine = { config, pkgs, ... }: {
+      services.redmine = {
+        enable = true;
+        package = pkgs.redmine;
+        database.type = type;
+        plugins = {
+          redmine_env_auth = pkgs.fetchurl {
+            url = "https://github.com/Intera/redmine_env_auth/archive/0.7.zip";
+            sha256 = "1xb8lyarc7mpi86yflnlgyllh9hfwb9z304f19dx409gqpia99sc";
+          };
+        };
+        themes = {
+          dkuk-redmine_alex_skin = pkgs.fetchurl {
+            url = "https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip";
+            sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("redmine.service")
+      machine.wait_for_open_port(3000)
+      machine.succeed("curl --fail http://localhost:3000/")
+    '';
+  } // {
+    meta.maintainers = [ maintainers.aanderse ];
+  };
+in {
+  mysql = redmineTest { name = "mysql"; type = "mysql2"; };
+  pgsql = redmineTest { name = "pgsql"; type = "postgresql"; };
+}
diff --git a/nixpkgs/nixos/tests/restart-by-activation-script.nix b/nixpkgs/nixos/tests/restart-by-activation-script.nix
new file mode 100644
index 000000000000..0ac079e0101e
--- /dev/null
+++ b/nixpkgs/nixos/tests/restart-by-activation-script.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "restart-by-activation-script";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ das_j ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    systemd.services.restart-me = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${pkgs.coreutils}/bin/true";
+      };
+    };
+
+    systemd.services.reload-me = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = rec {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${pkgs.coreutils}/bin/true";
+        ExecReload = ExecStart;
+      };
+    };
+
+    system.activationScripts.test = {
+      supportsDryActivation = true;
+      text = ''
+        if [ -e /test-the-activation-script ]; then
+          if [ "$NIXOS_ACTION" != dry-activate ]; then
+            touch /activation-was-run
+            echo restart-me.service > /run/nixos/activation-restart-list
+            echo reload-me.service > /run/nixos/activation-reload-list
+          else
+            echo restart-me.service > /run/nixos/dry-activation-restart-list
+            echo reload-me.service > /run/nixos/dry-activation-reload-list
+          fi
+        fi
+      '';
+    };
+  };
+
+  testScript = /* python */ ''
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("nothing happens when the activation script does nothing"):
+        out = machine.succeed("/run/current-system/bin/switch-to-configuration dry-activate 2>&1")
+        assert 'restart' not in out
+        assert 'reload' not in out
+        out = machine.succeed("/run/current-system/bin/switch-to-configuration test")
+        assert 'restart' not in out
+        assert 'reload' not in out
+
+    machine.succeed("touch /test-the-activation-script")
+
+    with subtest("dry activation"):
+        out = machine.succeed("/run/current-system/bin/switch-to-configuration dry-activate 2>&1")
+        assert 'would restart the following units: restart-me.service' in out
+        assert 'would reload the following units: reload-me.service' in out
+        machine.fail("test -f /run/nixos/dry-activation-restart-list")
+        machine.fail("test -f /run/nixos/dry-activation-reload-list")
+
+    with subtest("real activation"):
+        out = machine.succeed("/run/current-system/bin/switch-to-configuration test 2>&1")
+        assert 'restarting the following units: restart-me.service' in out
+        assert 'reloading the following units: reload-me.service' in out
+        machine.fail("test -f /run/nixos/activation-restart-list")
+        machine.fail("test -f /run/nixos/activation-reload-list")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/restic.nix b/nixpkgs/nixos/tests/restic.nix
new file mode 100644
index 000000000000..4111720cf6be
--- /dev/null
+++ b/nixpkgs/nixos/tests/restic.nix
@@ -0,0 +1,195 @@
+import ./make-test-python.nix (
+  { pkgs, ... }:
+
+  let
+    remoteRepository = "/root/restic-backup";
+    remoteFromFileRepository = "/root/restic-backup-from-file";
+    remoteNoInitRepository = "/root/restic-backup-no-init";
+    rcloneRepository = "rclone:local:/root/restic-rclone-backup";
+
+    backupPrepareCommand = ''
+      touch /root/backupPrepareCommand
+      test ! -e /root/backupCleanupCommand
+    '';
+
+    backupCleanupCommand = ''
+      rm /root/backupPrepareCommand
+      touch /root/backupCleanupCommand
+    '';
+
+    testDir = pkgs.stdenvNoCC.mkDerivation {
+      name = "test-files-to-backup";
+      unpackPhase = "true";
+      installPhase = ''
+        mkdir $out
+        echo some_file > $out/some_file
+        echo some_other_file > $out/some_other_file
+        mkdir $out/a_dir
+        echo a_file > $out/a_dir/a_file
+      '';
+    };
+
+    passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
+    paths = [ "/opt" ];
+    exclude = [ "/opt/excluded_file_*" ];
+    pruneOpts = [
+      "--keep-daily 2"
+      "--keep-weekly 1"
+      "--keep-monthly 1"
+      "--keep-yearly 99"
+    ];
+  in
+  {
+    name = "restic";
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bbigras i077 ];
+    };
+
+    nodes = {
+      server =
+        { pkgs, ... }:
+        {
+          services.restic.backups = {
+            remotebackup = {
+              inherit passwordFile paths exclude pruneOpts backupPrepareCommand backupCleanupCommand;
+              repository = remoteRepository;
+              initialize = true;
+              timerConfig = null; # has no effect here, just checking that it doesn't break the service
+            };
+            remote-from-file-backup = {
+              inherit passwordFile exclude pruneOpts;
+              initialize = true;
+              repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository;
+              paths = [ "/opt/a_dir" ];
+              dynamicFilesFrom = ''
+                find /opt -mindepth 1 -maxdepth 1 ! -name a_dir # all files in /opt except for a_dir
+              '';
+            };
+            remote-noinit-backup = {
+              inherit passwordFile exclude pruneOpts paths;
+              initialize = false;
+              repository = remoteNoInitRepository;
+            };
+            rclonebackup = {
+              inherit passwordFile paths exclude pruneOpts;
+              initialize = true;
+              repository = rcloneRepository;
+              rcloneConfig = {
+                type = "local";
+                one_file_system = true;
+              };
+
+              # This gets overridden by rcloneConfig.type
+              rcloneConfigFile = pkgs.writeText "rclone.conf" ''
+                [local]
+                type=ftp
+              '';
+            };
+            remoteprune = {
+              inherit passwordFile;
+              repository = remoteRepository;
+              pruneOpts = [ "--keep-last 1" ];
+            };
+            custompackage = {
+              inherit passwordFile paths;
+              repository = "some-fake-repository";
+              package = pkgs.writeShellScriptBin "restic" ''
+                echo "$@" >> /root/fake-restic.log;
+              '';
+
+              pruneOpts = [ "--keep-last 1" ];
+              checkOpts = [ "--some-check-option" ];
+            };
+          };
+
+          environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
+        };
+    };
+
+    testScript = ''
+      server.start()
+      server.wait_for_unit("dbus.socket")
+      server.fail(
+          "restic-remotebackup snapshots",
+          'restic-remote-from-file-backup snapshots"',
+          "restic-rclonebackup snapshots",
+          "grep 'backup.* /opt' /root/fake-restic.log",
+      )
+      server.succeed(
+          # set up
+          "cp -rT ${testDir} /opt",
+          "touch /opt/excluded_file_1 /opt/excluded_file_2",
+          "mkdir -p /root/restic-rclone-backup",
+          "restic-remote-noinit-backup init",
+
+          # test that remotebackup runs custom commands and produces a snapshot
+          "timedatectl set-time '2016-12-13 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /root/backupCleanupCommand",
+          'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that restoring that snapshot produces the same directory
+          "mkdir /tmp/restore-1",
+          "restic-remotebackup restore latest -t /tmp/restore-1",
+          "diff -ru ${testDir} /tmp/restore-1/opt",
+
+          # test that remote-from-file-backup produces a snapshot
+          "systemctl start restic-backups-remote-from-file-backup.service",
+          'restic-remote-from-file-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that remote-noinit-backup produces a snapshot
+          "systemctl start restic-backups-remote-noinit-backup.service",
+          'restic-remote-noinit-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that restoring that snapshot produces the same directory
+          "mkdir /tmp/restore-2",
+          "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} restore latest -t /tmp/restore-2",
+          "diff -ru ${testDir} /tmp/restore-2/opt",
+
+          # test that rclonebackup produces a snapshot
+          "systemctl start restic-backups-rclonebackup.service",
+          'restic-rclonebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that custompackage runs both `restic backup` and `restic check` with reasonable commandlines
+          "systemctl start restic-backups-custompackage.service",
+          "grep 'backup' /root/fake-restic.log",
+          "grep 'check.* --some-check-option' /root/fake-restic.log",
+
+          # test that we can create four snapshots in remotebackup and rclonebackup
+          "timedatectl set-time '2017-12-13 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /root/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          "timedatectl set-time '2018-12-13 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /root/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          "timedatectl set-time '2018-12-14 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /root/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          "timedatectl set-time '2018-12-15 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /root/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          "timedatectl set-time '2018-12-16 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /root/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+
+          'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"',
+          'restic-rclonebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"',
+
+          # test that remoteprune brings us back to 1 snapshot in remotebackup
+          "systemctl start restic-backups-remoteprune.service",
+          'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+      )
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/retroarch.nix b/nixpkgs/nixos/tests/retroarch.nix
new file mode 100644
index 000000000000..0e5f60aa8be2
--- /dev/null
+++ b/nixpkgs/nixos/tests/retroarch.nix
@@ -0,0 +1,49 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  {
+    name = "retroarch";
+    meta = with pkgs.lib; { maintainers = teams.libretro.members ++ [ maintainers.j0hax ]; };
+
+    nodes.machine = { ... }:
+
+      {
+        imports = [ ./common/user-account.nix ];
+        services.xserver.enable = true;
+        services.xserver.desktopManager.retroarch = {
+          enable = true;
+          package = pkgs.retroarchBare;
+        };
+        services.xserver.displayManager = {
+          sddm.enable = true;
+          defaultSession = "RetroArch";
+          autoLogin = {
+            enable = true;
+            user = "alice";
+          };
+        };
+      };
+
+    testScript = { nodes, ... }:
+      let
+        user = nodes.machine.config.users.users.alice;
+        xdo = "${pkgs.xdotool}/bin/xdotool";
+      in ''
+        with subtest("Wait for login"):
+            start_all()
+            machine.wait_for_file("/tmp/xauth_*")
+            machine.succeed("xauth merge /tmp/xauth_*")
+
+        with subtest("Check RetroArch started"):
+            machine.wait_until_succeeds("pgrep retroarch")
+            machine.wait_for_window("^RetroArch ")
+
+        with subtest("Check configuration created"):
+            machine.wait_for_file("${user.home}/.config/retroarch/retroarch.cfg")
+
+        with subtest("Wait to get a screenshot"):
+            machine.execute(
+                "${xdo} key Alt+F1 sleep 10"
+            )
+            machine.screenshot("screen")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/rkvm/cert.pem b/nixpkgs/nixos/tests/rkvm/cert.pem
new file mode 100644
index 000000000000..933efe520578
--- /dev/null
+++ b/nixpkgs/nixos/tests/rkvm/cert.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAcagAwIBAgIUWW1hb9xdRtxAhA42jkS89goW9LUwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEcmt2bTAeFw0yMzA4MjIxOTI1NDlaFw0zMzA4MTkxOTI1
+NDlaMA8xDTALBgNVBAMMBHJrdm0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCuBsh0+LDXN4b2o/PJjzuiZ9Yv9Pz1Oho9WRiXtNIuHTRdBCcht/iu3PGF
+ICIX+H3dqQOziGSCTAQGJD2p+1ik8d+boJbpa0oxXuHuomsMAT3mib3GpipQoBLP
+KaEbWEsvQbr3RMx8WOtG4dmRQFzSVVtmAXyM0pNyisd4eUCplyIl9gsRJIvsO/0M
+OkgOZW9XLfKiAWlZoyXEkBmPAshg3EkwQtmwxPA/NgWbAOW3zJKSChxnnGYiuIIu
+R/wJ8OQXHP6boQLQGUhCWBKa1uK1gEBmV3Pj6uK8RzTkQq6/47F5sPa6VfqQYdyl
+TCs9bSqHXZjqMBoiSp22uH6+Lh9RAgMBAAGjMjAwMA8GA1UdEQQIMAaHBAoAAAEw
+HQYDVR0OBBYEFEh9HEsnY3dfNKVyPWDbwfR0qHopMA0GCSqGSIb3DQEBCwUAA4IB
+AQB/r+K20JqegUZ/kepPxIU95YY81aUUoxvLbu4EAgh8o46Fgm75qrTZPg4TaIZa
+wtVejekrF+p3QVf0ErUblh/iCjTZPSzCmKHZt8cc9OwTH7bt3bx7heknzLDyIa5z
+szAL+6241UggQ5n5NUGn5+xZHA7TMe47xAZPaRMlCQ/tp5pWFjH6WSSQSP5t4Ag9
+ObhY+uudFjmWi3QIBTr3iIscbWx7tD8cjus7PzM7+kszSDRV04xb6Ox8JzW9MKIN
+GwgwVgs3zCuyqBmTGnR1og3aMk6VtlyZUYE78uuc+fMBxqoBZ0mykeOp0Tbzgtf7
+gPkYcQ6vonoQhuTXYj/NrY+b
+-----END CERTIFICATE-----
diff --git a/nixpkgs/nixos/tests/rkvm/default.nix b/nixpkgs/nixos/tests/rkvm/default.nix
new file mode 100644
index 000000000000..22425948d8bf
--- /dev/null
+++ b/nixpkgs/nixos/tests/rkvm/default.nix
@@ -0,0 +1,104 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+let
+  # Generated with
+  #
+  # nix shell .#rkvm --command "rkvm-certificate-gen --ip-addresses 10.0.0.1 cert.pem key.pem"
+  #
+  snakeoil-cert = ./cert.pem;
+  snakeoil-key = ./key.pem;
+in
+{
+  name = "rkvm";
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ ../common/user-account.nix ];
+
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.1/24";
+      };
+
+      services.getty.autologinUser = "alice";
+
+      services.rkvm.server = {
+        enable = true;
+        settings = {
+          certificate = snakeoil-cert;
+          key = snakeoil-key;
+          password = "snakeoil";
+          switch-keys = [ "left-alt" "right-alt" ];
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      imports = [ ../common/user-account.nix ];
+
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.2/24";
+      };
+
+      services.getty.autologinUser = "alice";
+
+      services.rkvm.client = {
+        enable = true;
+        settings = {
+          server = "10.0.0.1:5258";
+          certificate = snakeoil-cert;
+          key = snakeoil-key;
+          password = "snakeoil";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    server.wait_for_unit("getty@tty1.service")
+    server.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    server.wait_for_unit("rkvm-server")
+    server.wait_for_open_port(5258)
+
+    client.wait_for_unit("getty@tty1.service")
+    client.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    client.wait_for_unit("rkvm-client")
+
+    server.sleep(1)
+
+    # Switch to client
+    server.send_key("alt-alt_r", delay=0.2)
+    server.send_chars("echo 'hello client' > /tmp/test.txt\n")
+
+    # Switch to server
+    server.send_key("alt-alt_r", delay=0.2)
+    server.send_chars("echo 'hello server' > /tmp/test.txt\n")
+
+    server.sleep(1)
+
+    client.systemctl("stop rkvm-client.service")
+    server.systemctl("stop rkvm-server.service")
+
+    server_file = server.succeed("cat /tmp/test.txt")
+    assert server_file.strip() == "hello server"
+
+    client_file = client.succeed("cat /tmp/test.txt")
+    assert client_file.strip() == "hello client"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/rkvm/key.pem b/nixpkgs/nixos/tests/rkvm/key.pem
new file mode 100644
index 000000000000..7197decff8d3
--- /dev/null
+++ b/nixpkgs/nixos/tests/rkvm/key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCuBsh0+LDXN4b2
+o/PJjzuiZ9Yv9Pz1Oho9WRiXtNIuHTRdBCcht/iu3PGFICIX+H3dqQOziGSCTAQG
+JD2p+1ik8d+boJbpa0oxXuHuomsMAT3mib3GpipQoBLPKaEbWEsvQbr3RMx8WOtG
+4dmRQFzSVVtmAXyM0pNyisd4eUCplyIl9gsRJIvsO/0MOkgOZW9XLfKiAWlZoyXE
+kBmPAshg3EkwQtmwxPA/NgWbAOW3zJKSChxnnGYiuIIuR/wJ8OQXHP6boQLQGUhC
+WBKa1uK1gEBmV3Pj6uK8RzTkQq6/47F5sPa6VfqQYdylTCs9bSqHXZjqMBoiSp22
+uH6+Lh9RAgMBAAECggEABo2V1dBu5E51zsAiFCMdypdLZEyUNphvWC5h3oXowONz
+pH8ICYfXyEnkma/kk2+ALy0dSRDn6/94dVIUX7Fpx0hJCcoJyhSysK+TJWfIonqX
+ffYOMeFG8vicIgs+GFKs/hoPtB5LREbFkUqRj/EoWE6Y3aX3roaCwTZC8vaUk0OK
+54gExcNXRwQtFmfM9BiPT76F2J641NVsddgKumrryMi605CgZ57OFfSYEena6T3t
+JbQ1TKB3SH1LvSQIspyp56E3bjh8bcwSh72g88YxWZI9yarOesmyU+fXnmVqcBc+
+CiJDX3Te1C2GIkBiH3HZJo4P88aXrkJ7J8nub/812QKBgQDfCHjBy5uWzzbDnqZc
+cllIyUqMHq1iY2/btdZQbz83maZhQhH2UL4Zvoa7qgMX7Ou5jn1xpDaMeXNaajGK
+Fz66nmqQEUFX1i+2md2J8TeKD37yUJRdlrMiAc+RNp5wiOH9EI18g2m6h/nj3s/P
+MdNyxsz+wqOiJT0sZatarKiFhQKBgQDHv+lPy4OPH1MeSv5vmv3Pa41O/CeiPy+T
+gi6nEZayVRVog3zF9T6gNIHrZ1fdIppWPiPXv9fmC3s/IVEftLG6YC+MAfigYhiz
+Iceoal0iJJ8DglzOhlKgHEnxEwENCz8aJxjpvbxHHcpvgXdBSEVfHvVqDkAFTsvF
+JA5YTmqGXQKBgQCL6uqm2S7gq1o12p+PO4VbrjwAL3aiVLNl6Gtsxn2oSdIhDavr
+FLhNukMYFA4gwlcXb5au5k/6TG7bd+dgNDj8Jkm/27NcgVgpe9mJojQvfo0rQvXw
+yIvUd8JZ3SQEgTsU4X+Bb4eyp39TPwKrfxyh0qnj4QN6w1XfNmELX2nRaQKBgEq6
+a0ik9JTovSnKGKIcM/QTYow4HYO/a8cdnuJ13BDfb+DnwBg3BbTdr/UndmGOfnrh
+SHuAk/7GMNePWVApQ4xcS61vV1p5GJB7hLxm/my1kp+3d4z0B5lKvAbqeywsFvFr
+yxA3IWbhqEhLARh1Ny684EdLCXxy3Bzmvk8fFw8pAoGAGkt9pJC2wkk9fnJIHq+f
+h/WnEO0YrGzYnVA+RyCNKrimRd+GylGHJ/Ev6PRZvMwyGE7RCB+fHVrrEcEJAcxL
+SaOg5NA8cwrG+UpTQqi4gt6tCW87afVCyL6dC/E8giJlzI0LY9DnFGoVqYL0qJvm
+Sj4SU0fyLsW/csOLd5T+Bf8=
+-----END PRIVATE KEY-----
diff --git a/nixpkgs/nixos/tests/robustirc-bridge.nix b/nixpkgs/nixos/tests/robustirc-bridge.nix
new file mode 100644
index 000000000000..8493fd628212
--- /dev/null
+++ b/nixpkgs/nixos/tests/robustirc-bridge.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "robustirc-bridge";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hax404 ];
+  };
+
+  nodes =
+    { bridge =
+      { services.robustirc-bridge = {
+          enable = true;
+          extraFlags = [
+            "-listen localhost:6667"
+            "-network example.com"
+          ];
+        };
+      };
+    };
+
+    testScript =
+    ''
+      start_all()
+
+      bridge.wait_for_unit("robustirc-bridge.service")
+      bridge.wait_for_open_port(1080)
+      bridge.wait_for_open_port(6667)
+    '';
+})
diff --git a/nixpkgs/nixos/tests/rosenpass.nix b/nixpkgs/nixos/tests/rosenpass.nix
new file mode 100644
index 000000000000..ec4046c8c035
--- /dev/null
+++ b/nixpkgs/nixos/tests/rosenpass.nix
@@ -0,0 +1,217 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  deviceName = "rp0";
+
+  server = {
+    ip = "fe80::1";
+    wg = {
+      public = "mQufmDFeQQuU/fIaB2hHgluhjjm1ypK4hJr1cW3WqAw=";
+      secret = "4N5Y1dldqrpsbaEiY8O0XBUGUFf8vkvtBtm8AoOX7Eo=";
+      listen = 10000;
+    };
+  };
+  client = {
+    ip = "fe80::2";
+    wg = {
+      public = "Mb3GOlT7oS+F3JntVKiaD7SpHxLxNdtEmWz/9FMnRFU=";
+      secret = "uC5dfGMv7Oxf5UDfdPkj6rZiRZT2dRWp5x8IQxrNcUE=";
+    };
+  };
+in
+{
+  name = "rosenpass";
+
+  nodes =
+    let
+      shared = peer: { config, modulesPath, ... }: {
+        imports = [ "${modulesPath}/services/networking/rosenpass.nix" ];
+
+        boot.kernelModules = [ "wireguard" ];
+
+        services.rosenpass = {
+          enable = true;
+          defaultDevice = deviceName;
+          settings = {
+            verbosity = "Verbose";
+            public_key = "/etc/rosenpass/pqpk";
+            secret_key = "/etc/rosenpass/pqsk";
+          };
+        };
+
+        networking.firewall.allowedUDPPorts = [ 9999 ];
+
+        systemd.network = {
+          enable = true;
+          networks."rosenpass" = {
+            matchConfig.Name = deviceName;
+            networkConfig.IPForward = true;
+            address = [ "${peer.ip}/64" ];
+          };
+
+          netdevs."10-rp0" = {
+            netdevConfig = {
+              Kind = "wireguard";
+              Name = deviceName;
+            };
+            wireguardConfig.PrivateKeyFile = "/etc/wireguard/wgsk";
+          };
+        };
+
+        environment.etc."wireguard/wgsk" = {
+          text = peer.wg.secret;
+          user = "systemd-network";
+          group = "systemd-network";
+        };
+      };
+    in
+    {
+      server = {
+        imports = [ (shared server) ];
+
+        networking.firewall.allowedUDPPorts = [ server.wg.listen ];
+
+        systemd.network.netdevs."10-${deviceName}" = {
+          wireguardConfig.ListenPort = server.wg.listen;
+          wireguardPeers = [
+            {
+              wireguardPeerConfig = {
+                AllowedIPs = [ "::/0" ];
+                PublicKey = client.wg.public;
+              };
+            }
+          ];
+        };
+
+        services.rosenpass.settings = {
+          listen = [ "0.0.0.0:9999" ];
+          peers = [
+            {
+              public_key = "/etc/rosenpass/peers/client/pqpk";
+              peer = client.wg.public;
+            }
+          ];
+        };
+      };
+      client = {
+        imports = [ (shared client) ];
+
+        systemd.network.netdevs."10-${deviceName}".wireguardPeers = [
+          {
+            wireguardPeerConfig = {
+              AllowedIPs = [ "::/0" ];
+              PublicKey = server.wg.public;
+              Endpoint = "server:${builtins.toString server.wg.listen}";
+            };
+          }
+        ];
+
+        services.rosenpass.settings.peers = [
+          {
+            public_key = "/etc/rosenpass/peers/server/pqpk";
+            endpoint = "server:9999";
+            peer = server.wg.public;
+          }
+        ];
+      };
+    };
+
+  testScript = { ... }: ''
+    from os import system
+
+    # Full path to rosenpass in the store, to avoid fiddling with `$PATH`.
+    rosenpass = "${pkgs.rosenpass}/bin/rosenpass"
+
+    # Path in `/etc` where keys will be placed.
+    etc = "/etc/rosenpass"
+
+    start_all()
+
+    for machine in [server, client]:
+        machine.wait_for_unit("multi-user.target")
+
+    # Gently stop Rosenpass to avoid crashes during key generation/distribution.
+    for machine in [server, client]:
+        machine.execute("systemctl stop rosenpass.service")
+
+    for (name, machine, remote) in [("server", server, client), ("client", client, server)]:
+        pk, sk = f"{name}.pqpk", f"{name}.pqsk"
+        system(f"{rosenpass} gen-keys --force --secret-key {sk} --public-key {pk}")
+        machine.copy_from_host(sk, f"{etc}/pqsk")
+        machine.copy_from_host(pk, f"{etc}/pqpk")
+        remote.copy_from_host(pk, f"{etc}/peers/{name}/pqpk")
+
+    for machine in [server, client]:
+        machine.execute("systemctl start rosenpass.service")
+
+    for machine in [server, client]:
+        machine.wait_for_unit("rosenpass.service")
+
+    with subtest("ping"):
+        client.succeed("ping -c 2 -i 0.5 ${server.ip}%${deviceName}")
+
+    with subtest("preshared-keys"):
+        # Rosenpass works by setting the WireGuard preshared key at regular intervals.
+        # Thus, if it is not active, then no key will be set, and the output of `wg show` will contain "none".
+        # Otherwise, if it is active, then the key will be set and "none" will not be found in the output of `wg show`.
+        for machine in [server, client]:
+            machine.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5)
+  '';
+
+  # NOTE: Below configuration is for "interactive" (=developing/debugging) only.
+  interactive.nodes =
+    let
+      inherit (import ./ssh-keys.nix pkgs) snakeOilPublicKey snakeOilPrivateKey;
+
+      sshAndKeyGeneration = {
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+        environment.systemPackages = [
+          (pkgs.writeShellApplication {
+            name = "gen-keys";
+            runtimeInputs = [ pkgs.rosenpass ];
+            text = ''
+              HOST="$(hostname)"
+              if [ "$HOST" == "server" ]
+              then
+                PEER="client"
+              else
+                PEER="server"
+              fi
+
+              # Generate keypair.
+              mkdir -vp /etc/rosenpass/peers/$PEER
+              rosenpass gen-keys --force --secret-key /etc/rosenpass/pqsk --public-key /etc/rosenpass/pqpk
+
+              # Set up SSH key.
+              mkdir -p /root/.ssh
+              cp ${snakeOilPrivateKey} /root/.ssh/id_ecdsa
+              chmod 0400 /root/.ssh/id_ecdsa
+
+              # Copy public key to other peer.
+              # shellcheck disable=SC2029
+              ssh -o StrictHostKeyChecking=no $PEER "mkdir -pv /etc/rosenpass/peers/$HOST"
+              scp /etc/rosenpass/pqpk "$PEER:/etc/rosenpass/peers/$HOST/pqpk"
+            '';
+          })
+        ];
+      };
+
+      # Use kmscon <https://www.freedesktop.org/wiki/Software/kmscon/>
+      # to provide a slightly nicer console, and while we're at it,
+      # also use a nice font.
+      # With kmscon, we can for example zoom in/out using [Ctrl] + [+]
+      # and [Ctrl] + [-]
+      niceConsoleAndAutologin.services.kmscon = {
+        enable = true;
+        autologinUser = "root";
+        fonts = [{
+          name = "Fira Code";
+          package = pkgs.fira-code;
+        }];
+      };
+    in
+    {
+      server = sshAndKeyGeneration // niceConsoleAndAutologin;
+      client = sshAndKeyGeneration // niceConsoleAndAutologin;
+    };
+})
diff --git a/nixpkgs/nixos/tests/roundcube.nix b/nixpkgs/nixos/tests/roundcube.nix
new file mode 100644
index 000000000000..763f10a7a2dd
--- /dev/null
+++ b/nixpkgs/nixos/tests/roundcube.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "roundcube";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ globin ];
+  };
+
+  nodes = {
+    roundcube = { config, pkgs, ... }: {
+      services.roundcube = {
+        enable = true;
+        hostName = "roundcube";
+        database.password = "not production";
+        package = pkgs.roundcube.withPlugins (plugins: [ plugins.persistent_login ]);
+        plugins = [ "persistent_login" ];
+        dicts = with pkgs.aspellDicts; [ en fr de ];
+      };
+      services.nginx.virtualHosts.roundcube = {
+        forceSSL = false;
+        enableACME = false;
+      };
+    };
+  };
+
+  testScript = ''
+    roundcube.start
+    roundcube.wait_for_unit("postgresql.service")
+    roundcube.wait_for_unit("phpfpm-roundcube.service")
+    roundcube.wait_for_unit("nginx.service")
+    roundcube.succeed("curl -sSfL http://roundcube/ | grep 'Keep me logged in'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/rshim.nix b/nixpkgs/nixos/tests/rshim.nix
new file mode 100644
index 000000000000..bb5cce028ae7
--- /dev/null
+++ b/nixpkgs/nixos/tests/rshim.nix
@@ -0,0 +1,25 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  basic = makeTest {
+    name = "rshim";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.rshim.enable = true;
+    };
+
+    testScript = { nodes, ... }: ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      print(machine.succeed("systemctl status rshim.service"))
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/rspamd-trainer.nix b/nixpkgs/nixos/tests/rspamd-trainer.nix
new file mode 100644
index 000000000000..9c157903d24b
--- /dev/null
+++ b/nixpkgs/nixos/tests/rspamd-trainer.nix
@@ -0,0 +1,155 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in {
+  name = "rspamd-trainer";
+  meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
+
+  nodes = {
+    machine = { options, config, ... }: {
+
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+
+      networking.extraHosts = ''
+        127.0.0.1 ${domain}
+     '';
+
+      services.rspamd-trainer = {
+        enable = true;
+        settings = {
+          HOST = domain;
+          USERNAME = "spam@${domain}";
+          INBOXPREFIX = "INBOX/";
+        };
+        secrets = [
+          # Do not use this in production. This will make passwords
+          # world-readable in the Nix store
+          "${pkgs.writeText "secrets" ''
+            PASSWORD = test123
+          ''}"
+        ];
+      };
+
+      services.maddy = {
+        enable = true;
+        hostname = domain;
+        primaryDomain = domain;
+        ensureAccounts = [ "spam@${domain}" ];
+        ensureCredentials = {
+          # Do not use this in production. This will make passwords world-readable
+          # in the Nix store
+          "spam@${domain}".passwordFile = "${pkgs.writeText "postmaster" "test123"}";
+        };
+        tls = {
+          loader = "file";
+          certificates = [{
+            certPath = "${certs.${domain}.cert}";
+            keyPath = "${certs.${domain}.key}";
+          }];
+        };
+        config = builtins.replaceStrings [
+          "imap tcp://0.0.0.0:143"
+          "submission tcp://0.0.0.0:587"
+        ] [
+          "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
+          "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
+        ] options.services.maddy.config.default;
+      };
+
+      services.rspamd = {
+        enable = true;
+        locals = {
+          "redis.conf".text = ''
+            servers = "${config.services.redis.servers.rspamd.unixSocket}";
+          '';
+          "classifier-bayes.conf".text = ''
+            backend = "redis";
+            autolearn = true;
+          '';
+        };
+      };
+
+      services.redis.servers.rspamd = {
+        enable = true;
+        port = 0;
+        unixSocket = "/run/redis-rspamd/redis.sock";
+        user = config.services.rspamd.user;
+      };
+
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "send-testmail" { } ''
+          import smtplib
+          import ssl
+          from email.mime.text import MIMEText
+          context = ssl.create_default_context()
+          msg = MIMEText("Hello World")
+          msg['Subject'] = 'Test'
+          msg['From'] = "spam@${domain}"
+          msg['To'] = "spam@${domain}"
+          with smtplib.SMTP_SSL(host='${domain}', port=465, context=context) as smtp:
+              smtp.login('spam@${domain}', 'test123')
+              smtp.sendmail(
+                'spam@${domain}', 'spam@${domain}', msg.as_string()
+              )
+        '')
+        (pkgs.writers.writePython3Bin "create-mail-dirs" { } ''
+          import imaplib
+          with imaplib.IMAP4_SSL('${domain}') as imap:
+              imap.login('spam@${domain}', 'test123')
+              imap.create("\"INBOX/report_spam\"")
+              imap.create("\"INBOX/report_ham\"")
+              imap.create("\"INBOX/report_spam_reply\"")
+              imap.select("INBOX")
+              imap.copy("1", "\"INBOX/report_ham\"")
+              imap.logout()
+        '')
+        (pkgs.writers.writePython3Bin "test-imap" { } ''
+          import imaplib
+          with imaplib.IMAP4_SSL('${domain}') as imap:
+              imap.login('spam@${domain}', 'test123')
+              imap.select("INBOX/learned_ham")
+              status, refs = imap.search(None, 'ALL')
+              assert status == 'OK'
+              assert len(refs) == 1
+              status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+              assert status == 'OK'
+              assert msg[0][1].strip() == b"Hello World"
+              imap.logout()
+        '')
+      ];
+
+
+
+    };
+
+  };
+
+  testScript = { nodes }: ''
+    start_all()
+    machine.wait_for_unit("maddy.service")
+    machine.wait_for_open_port(143)
+    machine.wait_for_open_port(993)
+    machine.wait_for_open_port(587)
+    machine.wait_for_open_port(465)
+
+    # Send test mail to spam@domain
+    machine.succeed("send-testmail")
+
+    # Create mail directories required for rspamd-trainer and copy mail from
+    # INBOX into INBOX/report_ham
+    machine.succeed("create-mail-dirs")
+
+    # Start rspamd-trainer. It should read mail from INBOX/report_ham
+    machine.wait_for_unit("rspamd.service")
+    machine.wait_for_unit("redis-rspamd.service")
+    machine.wait_for_file("/run/rspamd/rspamd.sock")
+    machine.succeed("systemctl start rspamd-trainer.service")
+
+    # Check if mail got processed by rspamd-trainer successfully and check for
+    # it in INBOX/learned_ham
+    machine.succeed("test-imap")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/rspamd.nix b/nixpkgs/nixos/tests/rspamd.nix
new file mode 100644
index 000000000000..26895fbad3f3
--- /dev/null
+++ b/nixpkgs/nixos/tests/rspamd.nix
@@ -0,0 +1,313 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  initMachine = ''
+    start_all()
+    machine.wait_for_unit("rspamd.service")
+    machine.succeed("id rspamd >/dev/null")
+  '';
+  checkSocket = socket: user: group: mode: ''
+    machine.succeed(
+        "ls ${socket} >/dev/null",
+        '[[ "$(stat -c %U ${socket})" == "${user}" ]]',
+        '[[ "$(stat -c %G ${socket})" == "${group}" ]]',
+        '[[ "$(stat -c %a ${socket})" == "${mode}" ]]',
+    )
+  '';
+  simple = name: enableIPv6: makeTest {
+    name = "rspamd-${name}";
+    nodes.machine = {
+      services.rspamd.enable = true;
+      networking.enableIPv6 = enableIPv6;
+    };
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_open_port(11334)
+      machine.wait_for_unit("rspamd.service")
+      machine.succeed("id rspamd >/dev/null")
+      ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" }
+      machine.sleep(10)
+      machine.log(machine.succeed("cat /etc/rspamd/rspamd.conf"))
+      machine.log(
+          machine.succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf")
+      )
+      machine.log(machine.succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"))
+      machine.log(machine.succeed("systemctl cat rspamd.service"))
+      machine.log(machine.succeed("curl http://localhost:11334/auth"))
+      machine.log(machine.succeed("curl http://127.0.0.1:11334/auth"))
+      ${optionalString enableIPv6 ''machine.log(machine.succeed("curl http://[::1]:11334/auth"))''}
+      # would not reformat
+    '';
+  };
+in
+{
+  simple = simple "simple" true;
+  ipv4only = simple "ipv4only" false;
+  deprecated = makeTest {
+    name = "rspamd-deprecated";
+    nodes.machine = {
+      services.rspamd = {
+        enable = true;
+        workers.normal.bindSockets = [{
+          socket = "/run/rspamd/rspamd.sock";
+          mode = "0600";
+          owner = "rspamd";
+          group = "rspamd";
+        }];
+        workers.controller.bindSockets = [{
+          socket = "/run/rspamd/rspamd-worker.sock";
+          mode = "0666";
+          owner = "rspamd";
+          group = "rspamd";
+        }];
+      };
+    };
+
+    testScript = ''
+      ${initMachine}
+      machine.wait_for_file("/run/rspamd/rspamd.sock")
+      ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "600" }
+      ${checkSocket "/run/rspamd/rspamd-worker.sock" "rspamd" "rspamd" "666" }
+      machine.log(machine.succeed("cat /etc/rspamd/rspamd.conf"))
+      machine.log(
+          machine.succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf")
+      )
+      machine.log(machine.succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"))
+      machine.log(machine.succeed("rspamc -h /run/rspamd/rspamd-worker.sock stat"))
+      machine.log(
+          machine.succeed(
+              "curl --unix-socket /run/rspamd/rspamd-worker.sock http://localhost/ping"
+          )
+      )
+    '';
+  };
+
+  bindports = makeTest {
+    name = "rspamd-bindports";
+    nodes.machine = {
+      services.rspamd = {
+        enable = true;
+        workers.normal.bindSockets = [{
+          socket = "/run/rspamd/rspamd.sock";
+          mode = "0600";
+          owner = "rspamd";
+          group = "rspamd";
+        }];
+        workers.controller.bindSockets = [{
+          socket = "/run/rspamd/rspamd-worker.sock";
+          mode = "0666";
+          owner = "rspamd";
+          group = "rspamd";
+        }];
+        workers.controller2 = {
+          type = "controller";
+          bindSockets = [ "0.0.0.0:11335" ];
+          extraConfig = ''
+            static_dir = "''${WWWDIR}";
+            secure_ip = null;
+            password = "verysecretpassword";
+          '';
+        };
+      };
+    };
+
+    testScript = ''
+      ${initMachine}
+      machine.wait_for_file("/run/rspamd/rspamd.sock")
+      ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "600" }
+      ${checkSocket "/run/rspamd/rspamd-worker.sock" "rspamd" "rspamd" "666" }
+      machine.log(machine.succeed("cat /etc/rspamd/rspamd.conf"))
+      machine.log(
+          machine.succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf")
+      )
+      machine.log(machine.succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"))
+      machine.log(
+          machine.succeed(
+              "grep 'LOCAL_CONFDIR/override.d/worker-controller2.inc' /etc/rspamd/rspamd.conf"
+          )
+      )
+      machine.log(
+          machine.succeed(
+              "grep 'verysecretpassword' /etc/rspamd/override.d/worker-controller2.inc"
+          )
+      )
+      machine.wait_until_succeeds(
+          "journalctl -u rspamd | grep -i 'starting controller process' >&2"
+      )
+      machine.log(machine.succeed("rspamc -h /run/rspamd/rspamd-worker.sock stat"))
+      machine.log(
+          machine.succeed(
+              "curl --unix-socket /run/rspamd/rspamd-worker.sock http://localhost/ping"
+          )
+      )
+      machine.log(machine.succeed("curl http://localhost:11335/ping"))
+    '';
+  };
+  customLuaRules = makeTest {
+    name = "rspamd-custom-lua-rules";
+    nodes.machine = {
+      environment.etc."tests/no-muh.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<mah@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
+      '';
+      environment.etc."tests/muh.eml".text = ''
+        From: Cow<cow@example.com>
+        To: Sheep2<mah@example.com>
+        Subject: Evil cows
+
+        Cows are majestic creatures don't Muh agree?
+      '';
+      services.rspamd = {
+        enable = true;
+        locals = {
+          "antivirus.conf" = mkIf false { text = ''
+              clamav {
+                action = "reject";
+                symbol = "CLAM_VIRUS";
+                type = "clamav";
+                log_clean = true;
+                servers = "/run/clamav/clamd.ctl";
+              }
+            '';};
+          "redis.conf" = {
+            enable = false;
+            text = ''
+              servers = "127.0.0.1";
+            '';
+          };
+          "groups.conf".text = ''
+            group "cows" {
+              symbol {
+                NO_MUH = {
+                  weight = 1.0;
+                  description = "Mails should not muh";
+                }
+              }
+            }
+          '';
+        };
+        localLuaRules = pkgs.writeText "rspamd.local.lua" ''
+          local rspamd_logger = require "rspamd_logger"
+          rspamd_config.NO_MUH = {
+            callback = function (task)
+              local parts = task:get_text_parts()
+              if parts then
+                for _,part in ipairs(parts) do
+                  local content = tostring(part:get_content())
+                  rspamd_logger.infox(rspamd_config, 'Found content %s', content)
+                  local found = string.find(content, "Muh");
+                  rspamd_logger.infox(rspamd_config, 'Found muh %s', tostring(found))
+                  if found then
+                    return true
+                  end
+                end
+              end
+              return false
+            end,
+            score = 5.0,
+            description = 'Allow no cows',
+            group = "cows",
+          }
+          rspamd_logger.infox(rspamd_config, 'Work dammit!!!')
+        '';
+      };
+    };
+    testScript = ''
+      ${initMachine}
+      machine.wait_for_open_port(11334)
+      machine.log(machine.succeed("cat /etc/rspamd/rspamd.conf"))
+      machine.log(machine.succeed("cat /etc/rspamd/rspamd.local.lua"))
+      machine.log(machine.succeed("cat /etc/rspamd/local.d/groups.conf"))
+      # Verify that redis.conf was not written
+      machine.fail("cat /etc/rspamd/local.d/redis.conf >&2")
+      # Verify that antivirus.conf was not written
+      machine.fail("cat /etc/rspamd/local.d/antivirus.conf >&2")
+      ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" }
+      machine.log(
+          machine.succeed("curl --unix-socket /run/rspamd/rspamd.sock http://localhost/ping")
+      )
+      machine.log(machine.succeed("rspamc -h 127.0.0.1:11334 stat"))
+      machine.log(machine.succeed("cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334"))
+      machine.log(
+          machine.succeed("cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols")
+      )
+      machine.wait_until_succeeds("journalctl -u rspamd | grep -i muh >&2")
+      machine.log(
+          machine.fail(
+              "cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH"
+          )
+      )
+      machine.log(
+          machine.succeed(
+              "cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH"
+          )
+      )
+    '';
+  };
+  postfixIntegration = makeTest {
+    name = "rspamd-postfix-integration";
+    nodes.machine = {
+      environment.systemPackages = with pkgs; [ msmtp ];
+      environment.etc."tests/gtube.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<tester@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
+
+        XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
+      '';
+      environment.etc."tests/example.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<tester@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
+      '';
+      users.users.tester = {
+        isNormalUser = true;
+        password = "test";
+      };
+      services.postfix = {
+        enable = true;
+        destination = ["example.com"];
+      };
+      services.rspamd = {
+        enable = true;
+        postfix.enable = true;
+        workers.rspamd_proxy.type = "rspamd_proxy";
+      };
+    };
+    testScript = ''
+      ${initMachine}
+      machine.wait_for_open_port(11334)
+      machine.wait_for_open_port(25)
+      ${checkSocket "/run/rspamd/rspamd-milter.sock" "rspamd" "postfix" "660" }
+      machine.log(machine.succeed("rspamc -h 127.0.0.1:11334 stat"))
+      machine.log(
+          machine.succeed(
+              "msmtp --host=localhost -t --read-envelope-from < /etc/tests/example.eml"
+          )
+      )
+      machine.log(
+          machine.fail(
+              "msmtp --host=localhost -t --read-envelope-from < /etc/tests/gtube.eml"
+          )
+      )
+
+      machine.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
+      machine.fail("journalctl -u postfix | grep -i error >&2")
+      machine.fail("journalctl -u postfix | grep -i warning >&2")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/rss2email.nix b/nixpkgs/nixos/tests/rss2email.nix
new file mode 100644
index 000000000000..60b27b95fabe
--- /dev/null
+++ b/nixpkgs/nixos/tests/rss2email.nix
@@ -0,0 +1,67 @@
+import ./make-test-python.nix {
+  name = "rss2email";
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      services.nginx = {
+        enable = true;
+        virtualHosts."127.0.0.1".root = ./common/webroot;
+      };
+      services.rss2email = {
+        enable = true;
+        to = "alice@localhost";
+        interval = "1";
+        config.from = "test@example.org";
+        feeds = {
+          nixos = { url = "http://127.0.0.1/news-rss.xml"; };
+        };
+      };
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          listen on 127.0.0.1
+          action dovecot_deliver mda \
+            "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
+        '';
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        mailLocation = "maildir:~/mail";
+        protocols = [ "imap" ];
+      };
+      environment.systemPackages = let
+        checkMailLanded = pkgs.writeScriptBin "check-mail-landed" ''
+          #!${pkgs.python3.interpreter}
+          import imaplib
+
+          with imaplib.IMAP4('127.0.0.1', 143) as imap:
+            imap.login('alice', 'foobar')
+            imap.select()
+            status, refs = imap.search(None, 'ALL')
+            print("=====> Result of search for all:", status, refs)
+            assert status == 'OK'
+            assert len(refs) > 0
+            status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+            assert status == 'OK'
+        '';
+      in [ pkgs.opensmtpd checkMailLanded ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.systemctl("start network-online.target")
+    server.wait_for_unit("network-online.target")
+    server.wait_for_unit("opensmtpd")
+    server.wait_for_unit("dovecot2")
+    server.wait_for_unit("nginx")
+    server.wait_for_unit("rss2email")
+
+    server.wait_until_succeeds("check-mail-landed >&2")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/rstudio-server.nix b/nixpkgs/nixos/tests/rstudio-server.nix
new file mode 100644
index 000000000000..dd5fe3e5b440
--- /dev/null
+++ b/nixpkgs/nixos/tests/rstudio-server.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  {
+    name = "rstudio-server-test";
+    meta.maintainers = with pkgs.lib.maintainers; [ jbedo cfhammill ];
+
+    nodes.machine = { config, lib, pkgs, ... }: {
+      services.rstudio-server.enable = true;
+    };
+
+    nodes.customPackageMachine = { config, lib, pkgs, ... }: {
+      services.rstudio-server = {
+        enable = true;
+        package = pkgs.rstudioServerWrapper.override { packages = [ pkgs.rPackages.ggplot2 ]; };
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("rstudio-server.service")
+      machine.succeed("curl -f -vvv -s http://127.0.0.1:8787")
+
+      customPackageMachine.wait_for_unit("rstudio-server.service")
+      customPackageMachine.succeed("curl -f -vvv -s http://127.0.0.1:8787")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/rsyncd.nix b/nixpkgs/nixos/tests/rsyncd.nix
new file mode 100644
index 000000000000..44464e42f28d
--- /dev/null
+++ b/nixpkgs/nixos/tests/rsyncd.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "rsyncd";
+  meta.maintainers = with pkgs.lib.maintainers; [ ehmry ];
+
+  nodes = let
+    mkNode = socketActivated:
+      { config, ... }: {
+        networking.firewall.allowedTCPPorts = [ config.services.rsyncd.port ];
+        services.rsyncd = {
+          enable = true;
+          inherit socketActivated;
+          settings = {
+            global = {
+              "reverse lookup" = false;
+              "forward lookup" = false;
+            };
+            tmp = {
+              path = "/nix/store";
+              comment = "test module";
+            };
+          };
+        };
+      };
+  in {
+    a = mkNode false;
+    b = mkNode true;
+  };
+
+  testScript = ''
+    start_all()
+    a.wait_for_unit("rsync")
+    b.wait_for_unit("sockets.target")
+    b.succeed("rsync a::")
+    a.succeed("rsync b::")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/rsyslogd.nix b/nixpkgs/nixos/tests/rsyslogd.nix
new file mode 100644
index 000000000000..049acdcd4393
--- /dev/null
+++ b/nixpkgs/nixos/tests/rsyslogd.nix
@@ -0,0 +1,40 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  test1 = makeTest {
+    name = "rsyslogd-test1";
+    meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.rsyslogd.enable = true;
+      services.journald.forwardToSyslog = false;
+    };
+
+    # ensure rsyslogd isn't receiving messages from journald if explicitly disabled
+    testScript = ''
+      machine.wait_for_unit("default.target")
+      machine.fail("test -f /var/log/messages")
+    '';
+  };
+
+  test2 = makeTest {
+    name = "rsyslogd-test2";
+    meta.maintainers = [ pkgs.lib.maintainers.aanderse ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.rsyslogd.enable = true;
+    };
+
+    # ensure rsyslogd is receiving messages from journald
+    testScript = ''
+      machine.wait_for_unit("default.target")
+      machine.succeed("test -f /var/log/messages")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/rxe.nix b/nixpkgs/nixos/tests/rxe.nix
new file mode 100644
index 000000000000..10753c4ed0c8
--- /dev/null
+++ b/nixpkgs/nixos/tests/rxe.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ ... } :
+
+let
+  node = { pkgs, ... } : {
+    networking = {
+      firewall = {
+        allowedUDPPorts = [ 4791 ]; # open RoCE port
+        allowedTCPPorts = [ 4800 ]; # port for test utils
+      };
+      rxe = {
+        enable = true;
+        interfaces = [ "eth1" ];
+      };
+    };
+
+    environment.systemPackages = with pkgs; [ rdma-core screen ];
+  };
+
+in {
+  name = "rxe";
+
+  nodes = {
+    server = node;
+    client = node;
+  };
+
+  testScript = ''
+    # Test if rxe interface comes up
+    server.wait_for_unit("default.target")
+    server.succeed("systemctl status rxe.service")
+    server.succeed("ibv_devices | grep rxe_eth1")
+
+    client.wait_for_unit("default.target")
+
+    # ping pong tests
+    for proto in "rc", "uc", "ud", "srq":
+        server.succeed(
+            "screen -dmS {0}_pingpong ibv_{0}_pingpong -p 4800 -s 1024 -g0".format(proto)
+        )
+        client.succeed("sleep 2; ibv_{}_pingpong -p 4800 -s 1024 -g0 server".format(proto))
+
+    server.succeed("screen -dmS rping rping -s -a server -C 10")
+    client.succeed("sleep 2; rping -c -a server -C 10")
+  '';
+})
+
+
diff --git a/nixpkgs/nixos/tests/sabnzbd.nix b/nixpkgs/nixos/tests/sabnzbd.nix
new file mode 100644
index 000000000000..64cb655b4315
--- /dev/null
+++ b/nixpkgs/nixos/tests/sabnzbd.nix
@@ -0,0 +1,25 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "sabnzbd";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [ jojosch ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    services.sabnzbd = {
+      enable = true;
+    };
+
+    # unrar is unfree
+    nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "unrar" ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("sabnzbd.service")
+    machine.wait_until_succeeds(
+        "curl --fail -L http://localhost:8080/"
+    )
+    _, out = machine.execute("grep SABCTools /var/lib/sabnzbd/logs/sabnzbd.log")
+    machine.log(out)
+    machine.fail("grep 'SABCTools disabled: no correct version found!' /var/lib/sabnzbd/logs/sabnzbd.log")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/samba-wsdd.nix b/nixpkgs/nixos/tests/samba-wsdd.nix
new file mode 100644
index 000000000000..666a626d1b4a
--- /dev/null
+++ b/nixpkgs/nixos/tests/samba-wsdd.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "samba-wsdd";
+  meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
+
+  nodes = {
+    client_wsdd = { pkgs, ... }: {
+      services.samba-wsdd = {
+        enable = true;
+        openFirewall = true;
+        interface = "eth1";
+        workgroup = "WORKGROUP";
+        hostname = "CLIENT-WSDD";
+        discovery = true;
+        extraOptions = [ "--no-host" ];
+      };
+    };
+
+    server_wsdd = { ... }: {
+      services.samba-wsdd = {
+        enable = true;
+        openFirewall = true;
+        interface = "eth1";
+        workgroup = "WORKGROUP";
+        hostname = "SERVER-WSDD";
+      };
+    };
+  };
+
+  testScript = ''
+    client_wsdd.start()
+    client_wsdd.wait_for_unit("samba-wsdd")
+
+    server_wsdd.start()
+    server_wsdd.wait_for_unit("samba-wsdd")
+
+    client_wsdd.wait_until_succeeds(
+        "echo list | ${pkgs.libressl.nc}/bin/nc -N -U /run/wsdd/wsdd.sock | grep -i SERVER-WSDD"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/samba.nix b/nixpkgs/nixos/tests/samba.nix
new file mode 100644
index 000000000000..252c3dd9c76e
--- /dev/null
+++ b/nixpkgs/nixos/tests/samba.nix
@@ -0,0 +1,46 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "samba";
+
+  meta.maintainers = [ pkgs.lib.maintainers.eelco ];
+
+  nodes =
+    { client =
+        { pkgs, ... }:
+        { virtualisation.fileSystems =
+            { "/public" = {
+                fsType = "cifs";
+                device = "//server/public";
+                options = [ "guest" ];
+              };
+            };
+        };
+
+      server =
+        { ... }:
+        { services.samba.enable = true;
+          services.samba.openFirewall = true;
+          services.samba.shares.public =
+            { path = "/public";
+              "read only" = true;
+              browseable = "yes";
+              "guest ok" = "yes";
+              comment = "Public samba share.";
+            };
+        };
+    };
+
+  # client# [    4.542997] mount[777]: sh: systemd-ask-password: command not found
+
+  testScript =
+    ''
+      server.start()
+      server.wait_for_unit("samba.target")
+      server.succeed("mkdir -p /public; echo bar > /public/foo")
+
+      client.start()
+      client.wait_for_unit("remote-fs.target")
+      client.succeed("[[ $(cat /public/foo) = bar ]]")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/sane.nix b/nixpkgs/nixos/tests/sane.nix
new file mode 100644
index 000000000000..cba1b4d1dc4d
--- /dev/null
+++ b/nixpkgs/nixos/tests/sane.nix
@@ -0,0 +1,85 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+/*
+  SANE NixOS test
+  ===============
+  SANE is intrisically tied to hardware, so testing it is not straightforward.
+  However:
+  - a fake webcam can be created with v4l2loopback
+  - sane has a backend (v4l) to use a webcam as a scanner
+  This test creates a webcam /dev/video0, streams a still image with some text
+  through this webcam, uses SANE to scan from the webcam, and uses OCR to check
+  that the expected text was scanned.
+*/
+let
+  text = "66263666188646651519653683416";
+  fontsConf = pkgs.makeFontsConf {
+    fontDirectories = [
+      pkgs.dejavu_fonts.minimal
+    ];
+  };
+  # an image with black on white text spelling "${text}"
+  # for some reason, the test fails if it's jpg instead of png
+  # the font is quite large to make OCR easier
+  image = pkgs.runCommand "image.png"
+    {
+      # only imagemagickBig can render text
+      nativeBuildInputs = [ pkgs.imagemagickBig ];
+      FONTCONFIG_FILE = fontsConf;
+    } ''
+    magick -pointsize 100 label:${text} $out
+  '';
+in
+{
+  name = "sane";
+  nodes.machine = { pkgs, config, ... }: {
+    boot = {
+      # create /dev/video0 as a fake webcam whose content is filled by ffmpeg
+      extraModprobeConfig = ''
+        options v4l2loopback devices=1 max_buffers=2 exclusive_caps=1 card_label=VirtualCam
+      '';
+      kernelModules = [ "v4l2loopback" ];
+      extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ];
+    };
+    systemd.services.fake-webcam = {
+      wantedBy = [ "multi-user.target" ];
+      description = "fill /dev/video0 with ${image}";
+      /* HACK: /dev/video0 is a v4l2 only device, it misses one single v4l1
+      ioctl, VIDIOCSPICT. But sane only supports v4l1, so it will log that this
+      ioctl failed, and assume that the pixel format is Y8 (gray). So we tell
+      ffmpeg to produce this pixel format.
+      */
+      serviceConfig.ExecStart = [ "${pkgs.ffmpeg}/bin/ffmpeg -framerate 30 -re -stream_loop -1 -i ${image} -f v4l2 -pix_fmt gray /dev/video0" ];
+    };
+    hardware.sane.enable = true;
+    system.extraDependencies = [ image ];
+    environment.systemPackages = [
+      pkgs.fswebcam
+      pkgs.tesseract
+      pkgs.v4l-utils
+    ];
+    environment.variables.SANE_DEBUG_V4L = "128";
+  };
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("fake-webcam.service")
+
+    # the device only appears when ffmpeg starts producing frames
+    machine.wait_until_succeeds("scanimage -L | grep /dev/video0")
+
+    machine.succeed("scanimage -L >&2")
+
+    with subtest("debugging: /dev/video0 works"):
+      machine.succeed("v4l2-ctl --all >&2")
+      machine.succeed("fswebcam --no-banner /tmp/webcam.jpg")
+      machine.copy_from_vm("/tmp/webcam.jpg", "webcam")
+
+    # scan with the webcam
+    machine.succeed("scanimage -o /tmp/scan.png >&2")
+    machine.copy_from_vm("/tmp/scan.png", "scan")
+
+    # the image should contain "${text}"
+    output = machine.succeed("tesseract /tmp/scan.png -")
+    print(output)
+    assert "${text}" in output, f"expected text ${text} was not found, OCR found {output!r}"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sanoid.nix b/nixpkgs/nixos/tests/sanoid.nix
new file mode 100644
index 000000000000..411ebcead9f6
--- /dev/null
+++ b/nixpkgs/nixos/tests/sanoid.nix
@@ -0,0 +1,130 @@
+import ./make-test-python.nix ({ pkgs, ... }: let
+  inherit (import ./ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+
+  commonConfig = { pkgs, ... }: {
+    virtualisation.emptyDiskImages = [ 2048 ];
+    boot.supportedFilesystems = [ "zfs" ];
+    environment.systemPackages = [ pkgs.parted ];
+  };
+in {
+  name = "sanoid";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lopsided98 ];
+  };
+
+  nodes = {
+    source = { ... }: {
+      imports = [ commonConfig ];
+      networking.hostId = "daa82e91";
+
+      programs.ssh.extraConfig = ''
+        UserKnownHostsFile=/dev/null
+        StrictHostKeyChecking=no
+      '';
+
+      services.sanoid = {
+        enable = true;
+        templates.test = {
+          hourly = 12;
+          daily = 1;
+          monthly = 1;
+          yearly = 1;
+
+          autosnap = true;
+        };
+        datasets."pool/sanoid".use_template = [ "test" ];
+        datasets."pool/compat".useTemplate = [ "test" ];
+        extraArgs = [ "--verbose" ];
+      };
+
+      services.syncoid = {
+        enable = true;
+        sshKey = "/var/lib/syncoid/id_ecdsa";
+        commands = {
+          # Sync snapshot taken by sanoid
+          "pool/sanoid" = {
+            target = "root@target:pool/sanoid";
+            extraArgs = [ "--no-sync-snap" "--create-bookmark" ];
+          };
+          # Take snapshot and sync
+          "pool/syncoid".target = "root@target:pool/syncoid";
+
+          # Test pool without parent (regression test for https://github.com/NixOS/nixpkgs/pull/180111)
+          "pool".target = "root@target:pool/full-pool";
+
+          # Test backward compatible options (regression test for https://github.com/NixOS/nixpkgs/issues/181561)
+          "pool/compat" = {
+            target = "root@target:pool/compat";
+            extraArgs = [ "--no-sync-snap" ];
+          };
+        };
+      };
+    };
+    target = { ... }: {
+      imports = [ commonConfig ];
+      networking.hostId = "dcf39d36";
+
+      services.openssh.enable = true;
+      users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+    };
+  };
+
+  testScript = ''
+    source.succeed(
+        "mkdir /mnt",
+        "parted --script /dev/vdb -- mklabel msdos mkpart primary 1024M -1s",
+        "udevadm settle",
+        "zpool create pool -R /mnt /dev/vdb1",
+        "zfs create pool/sanoid",
+        "zfs create pool/compat",
+        "zfs create pool/syncoid",
+        "udevadm settle",
+    )
+    target.succeed(
+        "mkdir /mnt",
+        "parted --script /dev/vdb -- mklabel msdos mkpart primary 1024M -1s",
+        "udevadm settle",
+        "zpool create pool -R /mnt /dev/vdb1",
+        "udevadm settle",
+    )
+
+    source.succeed(
+        "mkdir -m 700 -p /var/lib/syncoid",
+        "cat '${snakeOilPrivateKey}' > /var/lib/syncoid/id_ecdsa",
+        "chmod 600 /var/lib/syncoid/id_ecdsa",
+        "chown -R syncoid:syncoid /var/lib/syncoid/",
+    )
+
+    assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set before snapshotting"
+    assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set before snapshotting"
+    assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set before snapshotting"
+
+    # Take snapshot with sanoid
+    source.succeed("touch /mnt/pool/sanoid/test.txt")
+    source.succeed("touch /mnt/pool/compat/test.txt")
+    source.systemctl("start --wait sanoid.service")
+
+    assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set after snapshotting"
+    assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set after snapshotting"
+    assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set after snapshotting"
+
+    # Sync snapshots
+    target.wait_for_open_port(22)
+    source.succeed("touch /mnt/pool/syncoid/test.txt")
+    source.systemctl("start --wait syncoid-pool-sanoid.service")
+    target.succeed("cat /mnt/pool/sanoid/test.txt")
+    source.systemctl("start --wait syncoid-pool-syncoid.service")
+    target.succeed("cat /mnt/pool/syncoid/test.txt")
+
+    source.systemctl("start --wait syncoid-pool.service")
+    target.succeed("[[ -d /mnt/pool/full-pool/syncoid ]]")
+
+    source.systemctl("start --wait syncoid-pool-compat.service")
+    target.succeed("cat /mnt/pool/compat/test.txt")
+
+    assert len(source.succeed("zfs allow pool")) == 0, "Pool shouldn't have delegated permissions set after syncing snapshots"
+    assert len(source.succeed("zfs allow pool/sanoid")) == 0, "Sanoid dataset shouldn't have delegated permissions set after syncing snapshots"
+    assert len(source.succeed("zfs allow pool/syncoid")) == 0, "Syncoid dataset shouldn't have delegated permissions set after syncing snapshots"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/scaphandre.nix b/nixpkgs/nixos/tests/scaphandre.nix
new file mode 100644
index 000000000000..f0a411748503
--- /dev/null
+++ b/nixpkgs/nixos/tests/scaphandre.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix {
+  name = "scaphandre";
+
+  nodes = {
+    scaphandre = { pkgs, ... } : {
+      boot.kernelModules = [ "intel_rapl_common" ];
+
+      environment.systemPackages = [ pkgs.scaphandre ];
+    };
+  };
+
+  testScript = { nodes, ... } : ''
+    scaphandre.start()
+    scaphandre.wait_until_succeeds(
+        "scaphandre stdout -t 4",
+    )
+  '';
+}
diff --git a/nixpkgs/nixos/tests/schleuder.nix b/nixpkgs/nixos/tests/schleuder.nix
new file mode 100644
index 000000000000..e57ef66bb8f9
--- /dev/null
+++ b/nixpkgs/nixos/tests/schleuder.nix
@@ -0,0 +1,126 @@
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in
+import ./make-test-python.nix {
+  name = "schleuder";
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.postfix = {
+      enable = true;
+      enableSubmission = true;
+      tlsTrustedAuthorities = "${certs.ca.cert}";
+      sslCert = "${certs.${domain}.cert}";
+      sslKey = "${certs.${domain}.key}";
+      inherit domain;
+      destination = [ domain ];
+      localRecipients = [ "root" "alice" "bob" ];
+    };
+    services.schleuder = {
+      enable = true;
+      # Don't do it like this in production! The point of this setting
+      # is to allow loading secrets from _outside_ the world-readable
+      # Nix store.
+      extraSettingsFile = pkgs.writeText "schleuder-api-keys.yml" ''
+        api:
+          valid_api_keys:
+            - fnord
+      '';
+      lists = [ "security@${domain}" ];
+      settings.api = {
+        tls_cert_file = "${certs.${domain}.cert}";
+        tls_key_file = "${certs.${domain}.key}";
+      };
+    };
+
+    environment.systemPackages = [
+      pkgs.gnupg
+      pkgs.msmtp
+      (pkgs.writeScriptBin "do-test" ''
+        #!${pkgs.runtimeShell}
+        set -exuo pipefail
+
+        # Generate a GPG key with no passphrase and export it
+        sudo -u alice gpg --passphrase-fd 0 --batch --yes --quick-generate-key 'alice@${domain}' rsa4096 sign,encr < <(echo)
+        sudo -u alice gpg --armor --export alice@${domain} > alice.asc
+        # Create a new mailing list with alice as the owner, and alice's key
+        schleuder-cli list new security@${domain} alice@${domain} alice.asc
+
+        # Send an email from a non-member of the list. Use --auto-from so we don't have to specify who it's from twice.
+        msmtp --auto-from security@${domain} --host=${domain} --port=25 --tls --tls-starttls <<EOF
+          Subject: really big security issue!!
+          From: root@${domain}
+
+          I found a big security problem!
+        EOF
+
+        # Wait for delivery
+        (set +o pipefail; journalctl -f -n 1000 -u postfix | grep -m 1 'delivered to maildir')
+
+        # There should be exactly one email
+        mail=(/var/spool/mail/alice/new/*)
+        [[ "''${#mail[@]}" = 1 ]]
+
+        # Find the fingerprint of the mailing list key
+        read list_key_fp address < <(schleuder-cli keys list security@${domain} | grep security@)
+        schleuder-cli keys export security@${domain} $list_key_fp > list.asc
+
+        # Import the key into alice's keyring, so we can verify it as well as decrypting
+        sudo -u alice gpg --import <list.asc
+        # And perform the decryption.
+        sudo -u alice gpg -d $mail >decrypted
+        # And check that the text matches.
+        grep "big security problem" decrypted
+      '')
+
+      # For debugging:
+      # pkgs.vim pkgs.openssl pkgs.sqliteinteractive
+    ];
+
+    security.pki.certificateFiles = [ certs.ca.cert ];
+
+    # Since we don't have internet here, use dnsmasq to provide MX records from /etc/hosts
+    services.dnsmasq = {
+      enable = true;
+      settings.selfmx = true;
+    };
+
+    networking.extraHosts = ''
+      127.0.0.1 ${domain}
+    '';
+
+    # schleuder-cli's config is not quite optimal in several ways:
+    # - A fingerprint _must_ be pinned, it doesn't even have an option
+    #   to trust the PKI
+    # - It compares certificate fingerprints rather than key
+    #   fingerprints, so renewals break the pin (though that's not
+    #   relevant for this test)
+    # - It compares them as strings, which means we need to match the
+    #   expected format exactly. This means removing the :s and
+    #   lowercasing it.
+    # Refs:
+    # https://0xacab.org/schleuder/schleuder-cli/-/issues/16
+    # https://0xacab.org/schleuder/schleuder-cli/-/blob/f8895b9f47083d8c7b99a2797c93f170f3c6a3c0/lib/schleuder-cli/helper.rb#L230-238
+    systemd.tmpfiles.rules = let cliconfig = pkgs.runCommand "schleuder-cli.yml"
+      {
+        nativeBuildInputs = [ pkgs.jq pkgs.openssl ];
+      } ''
+      fp=$(openssl x509 -in ${certs.${domain}.cert} -noout -fingerprint -sha256 | cut -d = -f 2 | tr -d : | tr 'A-Z' 'a-z')
+      cat > $out <<EOF
+      host: localhost
+      port: 4443
+      tls_fingerprint: "$fp"
+      api_key: fnord
+      EOF
+    ''; in
+      [
+        "L+ /root/.schleuder-cli/schleuder-cli.yml - - - - ${cliconfig}"
+      ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_until_succeeds("nc -z localhost 4443")
+    machine.succeed("do-test")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/scrutiny.nix b/nixpkgs/nixos/tests/scrutiny.nix
new file mode 100644
index 000000000000..33160a6b3088
--- /dev/null
+++ b/nixpkgs/nixos/tests/scrutiny.nix
@@ -0,0 +1,70 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "scrutiny";
+  meta.maintainers = with lib.maintainers; [ jnsgruk ];
+
+  nodes = {
+    machine = { self, pkgs, lib, ... }: {
+      services = {
+        scrutiny.enable = true;
+        scrutiny.collector.enable = true;
+      };
+
+      environment.systemPackages =
+        let
+          seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
+            {
+              libraries = with pkgs.python3Packages; [ selenium ];
+            } ''
+            from selenium import webdriver
+            from selenium.webdriver.common.by import By
+            from selenium.webdriver.firefox.options import Options
+            from selenium.webdriver.support.ui import WebDriverWait
+            from selenium.webdriver.support import expected_conditions as EC
+
+            options = Options()
+            options.add_argument("--headless")
+            service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+
+            driver = webdriver.Firefox(options=options, service=service)
+            driver.implicitly_wait(10)
+            driver.get("http://localhost:8080/web/dashboard")
+
+            wait = WebDriverWait(driver, 10).until(
+              EC.text_to_be_present_in_element(
+                (By.TAG_NAME, "body"), "Drive health at a glance")
+            )
+
+            body_text = driver.find_element(By.TAG_NAME, "body").text
+            assert "Temperature history for each device" in body_text
+
+            driver.close()
+          '';
+        in
+        with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
+    };
+  };
+  # This is the test code that will check if our service is running correctly:
+  testScript = ''
+    start_all()
+
+    # Wait for InfluxDB to be available
+    machine.wait_for_unit("influxdb2")
+    machine.wait_for_open_port(8086)
+
+    # Wait for Scrutiny to be available
+    machine.wait_for_unit("scrutiny")
+    machine.wait_for_open_port(8080)
+
+    # Ensure the API responds as we expect
+    output = machine.succeed("curl localhost:8080/api/health")
+    assert output == '{"success":true}'
+
+    # Start the collector service to send some metrics
+    collect = machine.succeed("systemctl start scrutiny-collector.service")
+
+    # Ensure the application is actually rendered by the Javascript
+    machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sddm.nix b/nixpkgs/nixos/tests/sddm.nix
new file mode 100644
index 000000000000..b6c05deac05e
--- /dev/null
+++ b/nixpkgs/nixos/tests/sddm.nix
@@ -0,0 +1,67 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (pkgs) lib;
+
+  tests = {
+    default = {
+      name = "sddm";
+
+      nodes.machine = { ... }: {
+        imports = [ ./common/user-account.nix ];
+        services.xserver.enable = true;
+        services.xserver.displayManager.sddm.enable = true;
+        services.xserver.displayManager.defaultSession = "none+icewm";
+        services.xserver.windowManager.icewm.enable = true;
+      };
+
+      enableOCR = true;
+
+      testScript = { nodes, ... }: let
+        user = nodes.machine.users.users.alice;
+      in ''
+        start_all()
+        machine.wait_for_text("(?i)select your user")
+        machine.screenshot("sddm")
+        machine.send_chars("${user.password}\n")
+        machine.wait_for_file("/tmp/xauth_*")
+        machine.succeed("xauth merge /tmp/xauth_*")
+        machine.wait_for_window("^IceWM ")
+      '';
+    };
+
+    autoLogin = {
+      name = "sddm-autologin";
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ ttuegel ];
+      };
+
+      nodes.machine = { ... }: {
+        imports = [ ./common/user-account.nix ];
+        services.xserver.enable = true;
+        services.xserver.displayManager = {
+          sddm.enable = true;
+          autoLogin = {
+            enable = true;
+            user = "alice";
+          };
+        };
+        services.xserver.displayManager.defaultSession = "none+icewm";
+        services.xserver.windowManager.icewm.enable = true;
+      };
+
+      testScript = { nodes, ... }: ''
+        start_all()
+        machine.wait_for_file("/tmp/xauth_*")
+        machine.succeed("xauth merge /tmp/xauth_*")
+        machine.wait_for_window("^IceWM ")
+      '';
+    };
+  };
+in
+  lib.mapAttrs (lib.const makeTest) tests
diff --git a/nixpkgs/nixos/tests/seafile.nix b/nixpkgs/nixos/tests/seafile.nix
new file mode 100644
index 000000000000..78e735f4fed7
--- /dev/null
+++ b/nixpkgs/nixos/tests/seafile.nix
@@ -0,0 +1,115 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    client = { config, pkgs, ... }: {
+      environment.systemPackages = [ pkgs.seafile-shared pkgs.curl ];
+    };
+  in {
+    name = "seafile";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ kampfschlaefer schmittlauch ];
+    };
+
+    nodes = {
+      server = { config, pkgs, ... }: {
+        services.seafile = {
+          enable = true;
+          ccnetSettings.General.SERVICE_URL = "http://server";
+          adminEmail = "admin@example.com";
+          initialAdminPassword = "seafile_password";
+        };
+        services.nginx = {
+          enable = true;
+          virtualHosts."server" = {
+            locations."/".proxyPass = "http://unix:/run/seahub/gunicorn.sock";
+            locations."/seafhttp" = {
+              proxyPass = "http://127.0.0.1:8082";
+              extraConfig = ''
+                rewrite ^/seafhttp(.*)$ $1 break;
+                client_max_body_size 0;
+                proxy_connect_timeout  36000s;
+                proxy_read_timeout  36000s;
+                proxy_send_timeout  36000s;
+                send_timeout  36000s;
+                proxy_http_version 1.1;
+              '';
+            };
+          };
+        };
+        networking.firewall = { allowedTCPPorts = [ 80 ]; };
+      };
+      client1 = client pkgs;
+      client2 = client pkgs;
+    };
+
+    testScript = ''
+      start_all()
+
+      with subtest("start seaf-server"):
+          server.wait_for_unit("seaf-server.service")
+          server.wait_for_file("/run/seafile/seafile.sock")
+
+      with subtest("start seahub"):
+          server.wait_for_unit("seahub.service")
+          server.wait_for_unit("nginx.service")
+          server.wait_for_file("/run/seahub/gunicorn.sock")
+
+      with subtest("client1 fetch seahub page"):
+          client1.succeed("curl -L http://server | grep 'Log In' >&2")
+
+      with subtest("client1 connect"):
+          client1.wait_for_unit("default.target")
+          client1.succeed("seaf-cli init -d . >&2")
+          client1.succeed("seaf-cli start >&2")
+          client1.succeed(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password >&2"
+          )
+
+          libid = client1.succeed(
+              'seaf-cli create -s http://server -n test01 -u admin\@example.com -p seafile_password -t "first test library"'
+          ).strip()
+
+          client1.succeed(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password |grep test01"
+          )
+          client1.fail(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password |grep test02"
+          )
+
+          client1.succeed(
+              f"seaf-cli download -l {libid} -s http://server -u admin\@example.com -p seafile_password -d . >&2"
+          )
+
+          client1.wait_until_succeeds("seaf-cli status |grep synchronized >&2")
+
+          client1.succeed("ls -la >&2")
+          client1.succeed("ls -la test01 >&2")
+
+          client1.execute("echo bla > test01/first_file")
+
+          client1.wait_until_succeeds("seaf-cli status |grep synchronized >&2")
+
+      with subtest("client2 sync"):
+          client2.wait_for_unit("default.target")
+
+          client2.succeed("seaf-cli init -d . >&2")
+          client2.succeed("seaf-cli start >&2")
+
+          client2.succeed(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password >&2"
+          )
+
+          libid = client2.succeed(
+              "seaf-cli list-remote -s http://server -u admin\@example.com -p seafile_password |grep test01 |cut -d' ' -f 2"
+          ).strip()
+
+          client2.succeed(
+              f"seaf-cli download -l {libid} -s http://server -u admin\@example.com -p seafile_password -d . >&2"
+          )
+
+          client2.wait_until_succeeds("seaf-cli status |grep synchronized >&2")
+
+          client2.succeed("ls -la test01 >&2")
+
+          client2.succeed('[ `cat test01/first_file` = "bla" ]')
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/searx.nix b/nixpkgs/nixos/tests/searx.nix
new file mode 100644
index 000000000000..2f808cb65266
--- /dev/null
+++ b/nixpkgs/nixos/tests/searx.nix
@@ -0,0 +1,114 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "searx";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ rnhmjoj ];
+  };
+
+  # basic setup: searx running the built-in webserver
+  nodes.base = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    services.searx = {
+      enable = true;
+      environmentFile = pkgs.writeText "secrets" ''
+        WOLFRAM_API_KEY  = sometoken
+        SEARX_SECRET_KEY = somesecret
+      '';
+
+      settings.server =
+        { port = "8080";
+          bind_address = "0.0.0.0";
+          secret_key = "@SEARX_SECRET_KEY@";
+        };
+      settings.engines = [
+        { name = "wolframalpha";
+          api_key = "@WOLFRAM_API_KEY@";
+          engine = "wolframalpha_api";
+        }
+        { name = "startpage";
+          shortcut = "start";
+        }
+      ];
+    };
+
+  };
+
+  # fancy setup: run in uWSGI and use nginx as proxy
+  nodes.fancy = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    services.searx = {
+      enable = true;
+      # searx refuses to run if unchanged
+      settings.server.secret_key = "somesecret";
+
+      runInUwsgi = true;
+      uwsgiConfig = {
+        # serve using the uwsgi protocol
+        socket = "/run/searx/uwsgi.sock";
+        chmod-socket = "660";
+
+        # use /searx as url "mountpoint"
+        mount = "/searx=searx.webapp:application";
+        module = "";
+        manage-script-name = true;
+      };
+    };
+
+    # use nginx as reverse proxy
+    services.nginx.enable = true;
+    services.nginx.virtualHosts.localhost = {
+      locations."/searx".extraConfig =
+        ''
+          include ${pkgs.nginx}/conf/uwsgi_params;
+          uwsgi_pass unix:/run/searx/uwsgi.sock;
+        '';
+      locations."/searx/static/".alias = "${pkgs.searx}/share/static/";
+    };
+
+    # allow nginx access to the searx socket
+    users.users.nginx.extraGroups = [ "searx" ];
+
+  };
+
+  testScript =
+    ''
+      base.start()
+
+      with subtest("Settings have been merged"):
+          base.wait_for_unit("searx-init")
+          base.wait_for_file("/run/searx/settings.yml")
+          output = base.succeed(
+              "${pkgs.yq-go}/bin/yq eval"
+              " '.engines[] | select(.name==\"startpage\") | .shortcut'"
+              " /run/searx/settings.yml"
+          ).strip()
+          assert output == "start", "Settings not merged"
+
+      with subtest("Environment variables have been substituted"):
+          base.succeed("grep -q somesecret /run/searx/settings.yml")
+          base.succeed("grep -q sometoken /run/searx/settings.yml")
+          base.copy_from_vm("/run/searx/settings.yml")
+
+      with subtest("Basic setup is working"):
+          base.wait_for_open_port(8080)
+          base.wait_for_unit("searx")
+          base.succeed(
+              "${pkgs.curl}/bin/curl --fail http://localhost:8080"
+          )
+          base.shutdown()
+
+      with subtest("Nginx+uWSGI setup is working"):
+          fancy.start()
+          fancy.wait_for_open_port(80)
+          fancy.wait_for_unit("uwsgi")
+          fancy.succeed(
+              "${pkgs.curl}/bin/curl --fail http://localhost/searx >&2"
+          )
+          fancy.succeed(
+              "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/themes/oscar/js/bootstrap.min.js >&2"
+          )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/seatd.nix b/nixpkgs/nixos/tests/seatd.nix
new file mode 100644
index 000000000000..138a6cb1cf44
--- /dev/null
+++ b/nixpkgs/nixos/tests/seatd.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  seatd-test = pkgs.writeShellApplication {
+    name = "seatd-client-pid";
+    text = ''
+      journalctl -u seatd --no-pager -b | while read -r line; do
+          case "$line" in
+          *"New client connected"*)
+              line="''${line##*pid: }"
+              pid="''${line%%,*}"
+              ;;
+          *"Opened client"*)
+              echo "$pid"
+              exit
+          esac
+      done;
+    '';
+  };
+in
+{
+  name = "seatd";
+  meta.maintainers = with lib.maintainers; [ sinanmohd ];
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.getty.autologinUser = "alice";
+    users.users.alice.extraGroups = [ "seat" "wheel" ];
+
+    fonts.enableDefaultPackages = true;
+    environment.systemPackages = with pkgs; [
+      dwl
+      foot
+      seatd-test
+    ];
+
+    programs.bash.loginShellInit = ''
+      [ "$(tty)" = "/dev/tty1" ] &&
+          dwl -s 'foot touch /tmp/foot_started'
+    '';
+
+    hardware.opengl.enable = true;
+    virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+    services.seatd.enable = true;
+  };
+
+  testScript = ''
+    machine.wait_for_file("/tmp/foot_started")
+    machine.succeed("test $(seatd-client-pid) = $(pgrep dwl)")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/service-runner.nix b/nixpkgs/nixos/tests/service-runner.nix
new file mode 100644
index 000000000000..79d96f739a6c
--- /dev/null
+++ b/nixpkgs/nixos/tests/service-runner.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "service-runner";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ roberth ];
+  };
+
+  nodes = {
+    machine = { pkgs, lib, ... }: {
+      services.nginx.enable = true;
+      services.nginx.virtualHosts.machine.root = pkgs.runCommand "webroot" {} ''
+        mkdir $out
+        echo 'yay' >$out/index.html
+      '';
+      systemd.services.nginx.enable = false;
+    };
+
+  };
+
+  testScript = { nodes, ... }: ''
+    url = "http://localhost/index.html"
+
+    with subtest("check systemd.services.nginx.runner"):
+        machine.fail(f"curl {url}")
+        machine.succeed(
+            """
+            mkdir -p /run/nginx /var/log/nginx /var/cache/nginx
+            ${nodes.machine.config.systemd.services.nginx.runner} >&2 &
+            echo $!>my-nginx.pid
+            """
+        )
+        machine.wait_for_open_port(80)
+        machine.succeed(f"curl -f {url}")
+        machine.succeed("kill -INT $(cat my-nginx.pid)")
+        machine.wait_for_closed_port(80)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sftpgo.nix b/nixpkgs/nixos/tests/sftpgo.nix
new file mode 100644
index 000000000000..a5bb1981d2c3
--- /dev/null
+++ b/nixpkgs/nixos/tests/sftpgo.nix
@@ -0,0 +1,382 @@
+# SFTPGo NixOS test
+#
+# This NixOS test sets up a basic test scenario for the SFTPGo module
+# and covers the following scenarios:
+# - uploading a file via sftp
+# - downloading the file over sftp
+# - assert that the ACLs are respected
+# - share a file between alice and bob (using sftp)
+# - assert that eve cannot acceess the shared folder between alice and bob.
+#
+# Additional test coverage for the remaining protocols (i.e. ftp, http and webdav)
+# would be a nice to have for the future.
+{ pkgs, lib, ...  }:
+
+let
+  inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+
+  # Returns an attributeset of users who are not system users.
+  normalUsers = config:
+    lib.filterAttrs (name: user: user.isNormalUser) config.users.users;
+
+  # Returns true if a user is a member of the given group
+  isMemberOf =
+    config:
+    # str
+    groupName:
+    # users.users attrset
+    user:
+      lib.any (x: x == user.name) config.users.groups.${groupName}.members;
+
+  # Generates a valid SFTPGo user configuration for a given user
+  # Will be converted to JSON and loaded on application startup.
+  generateUserAttrSet =
+    config:
+    # attrset returned by config.users.users.<username>
+    user: {
+      # 0: user is disabled, login is not allowed
+      # 1: user is enabled
+      status = 1;
+
+      username = user.name;
+      password = ""; # disables password authentication
+      public_keys = user.openssh.authorizedKeys.keys;
+      email = "${user.name}@example.com";
+
+      # User home directory on the local filesystem
+      home_dir = "${config.services.sftpgo.dataDir}/users/${user.name}";
+
+      # Defines a mapping between virtual SFTPGo paths and filesystem paths outside the user home directory.
+      #
+      # Supported for local filesystem only. If one or more of the specified folders are not
+      # inside the dataprovider they will be automatically created.
+      # You have to create the folder on the filesystem yourself
+      virtual_folders =
+        lib.optional (isMemberOf config sharedFolderName user) {
+          name = sharedFolderName;
+          mapped_path = "${config.services.sftpgo.dataDir}/${sharedFolderName}";
+          virtual_path = "/${sharedFolderName}";
+        };
+
+      # Defines the ACL on the virtual filesystem
+      permissions =
+        lib.recursiveUpdate {
+          "/" = [ "list" ];     # read-only top level directory
+          "/private" = [ "*" ]; # private subdirectory, not shared with others
+        } (lib.optionalAttrs (isMemberOf config "shared" user) {
+          "/shared" = [ "*" ];
+        });
+
+      filters = {
+        allowed_ip = [];
+        denied_ip = [];
+        web_client = [
+          "password-change-disabled"
+          "password-reset-disabled"
+          "api-key-auth-change-disabled"
+        ];
+      };
+
+      upload_bandwidth = 0; # unlimited
+      download_bandwidth = 0; # unlimited
+      expiration_date = 0; # means no expiration
+      max_sessions = 0;
+      quota_size = 0;
+      quota_files = 0;
+    };
+
+  # Generates a json file containing a static configuration
+  # of users and folders to import to SFTPGo.
+  loadDataJson = config: pkgs.writeText "users-and-folders.json" (builtins.toJSON {
+    users =
+      lib.mapAttrsToList (name: user: generateUserAttrSet config user) (normalUsers config);
+
+    folders = [
+      {
+        name = sharedFolderName;
+        description = "shared folder";
+
+        # 0: local filesystem
+        # 1: AWS S3 compatible
+        # 2: Google Cloud Storage
+        filesystem.provider = 0;
+
+        # Mapped path on the local filesystem
+        mapped_path = "${config.services.sftpgo.dataDir}/${sharedFolderName}";
+
+        # All users in the matching group gain access
+        users = config.users.groups.${sharedFolderName}.members;
+      }
+    ];
+  });
+
+  # Generated Host Key for connecting to SFTPGo's sftp subsystem.
+  snakeOilHostKey = pkgs.writeText "sftpgo_ed25519_host_key" ''
+    -----BEGIN OPENSSH PRIVATE KEY-----
+    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+    QyNTUxOQAAACBOtQu6U135yxtrvUqPoozUymkjoNNPVK6rqjS936RLtQAAAJAXOMoSFzjK
+    EgAAAAtzc2gtZWQyNTUxOQAAACBOtQu6U135yxtrvUqPoozUymkjoNNPVK6rqjS936RLtQ
+    AAAEAoRLEV1VD80mg314ObySpfrCcUqtWoOSS3EtMPPhx08U61C7pTXfnLG2u9So+ijNTK
+    aSOg009UrquqNL3fpEu1AAAADHNmdHBnb0BuaXhvcwE=
+    -----END OPENSSH PRIVATE KEY-----
+  '';
+
+  adminUsername = "admin";
+  adminPassword = "secretadminpassword";
+  aliceUsername = "alice";
+  alicePassword = "secretalicepassword";
+  bobUsername = "bob";
+  bobPassword = "secretbobpassword";
+  eveUsername = "eve";
+  evePassword = "secretevepassword";
+  sharedFolderName = "shared";
+
+  # A file for testing uploading via SFTP
+  testFile = pkgs.writeText "test.txt" "hello world";
+  sharedFile = pkgs.writeText "shared.txt" "shared content";
+
+  # Define the for exposing SFTP
+  sftpPort = 2022;
+
+  # Define the for exposing HTTP
+  httpPort = 8080;
+in
+{
+  name = "sftpgo";
+
+  meta.maintainers = with lib.maintainers; [ yayayayaka ];
+
+  nodes = {
+    server = { nodes, ... }: {
+      networking.firewall.allowedTCPPorts = [ sftpPort httpPort ];
+
+      # nodes.server.configure postgresql database
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "sftpgo" ];
+        ensureUsers = [{
+          name = "sftpgo";
+          ensureDBOwnership = true;
+        }];
+      };
+
+      services.sftpgo = {
+        enable = true;
+
+        loadDataFile = (loadDataJson nodes.server);
+
+        settings = {
+          data_provider = {
+            driver = "postgresql";
+            name = "sftpgo";
+            username = "sftpgo";
+            host = "/run/postgresql";
+            port = 5432;
+
+            # Enables the possibility to create an initial admin user on first startup.
+            create_default_admin = true;
+          };
+
+          httpd.bindings = [
+            {
+              address = ""; # listen on all interfaces
+              port = httpPort;
+              enable_https = false;
+
+              enable_web_client = true;
+              enable_web_admin = true;
+            }
+          ];
+
+          # Enable sftpd
+          sftpd = {
+            bindings = [{
+              address = ""; # listen on all interfaces
+              port = sftpPort;
+            }];
+            host_keys = [ snakeOilHostKey ];
+            password_authentication = false;
+            keyboard_interactive_authentication = false;
+          };
+        };
+      };
+
+      systemd.services.sftpgo = {
+        after = [ "postgresql.service"];
+        environment = {
+          # Update existing users
+          SFTPGO_LOADDATA_MODE = "0";
+          SFTPGO_DEFAULT_ADMIN_USERNAME = adminUsername;
+
+          # This will end up in cleartext in the systemd service.
+          # Don't use this approach in production!
+          SFTPGO_DEFAULT_ADMIN_PASSWORD = adminPassword;
+        };
+      };
+
+      # Sets up the folder hierarchy on the local filesystem
+      systemd.tmpfiles.rules =
+        let
+          sftpgoUser = nodes.server.services.sftpgo.user;
+          sftpgoGroup = nodes.server.services.sftpgo.group;
+          statePath = nodes.server.services.sftpgo.dataDir;
+        in [
+          # Create state directory
+          "d ${statePath} 0750 ${sftpgoUser} ${sftpgoGroup} -"
+          "d ${statePath}/users 0750 ${sftpgoUser} ${sftpgoGroup} -"
+
+          # Created shared folder directories
+          "d ${statePath}/${sharedFolderName} 2770 ${sftpgoUser} ${sharedFolderName}   -"
+        ]
+        ++ lib.mapAttrsToList (name: user:
+          # Create private user directories
+          ''
+            d ${statePath}/users/${user.name} 0700 ${sftpgoUser} ${sftpgoGroup} -
+            d ${statePath}/users/${user.name}/private 0700 ${sftpgoUser} ${sftpgoGroup} -
+          ''
+        ) (normalUsers nodes.server);
+
+      users.users =
+        let
+          commonAttrs = {
+            isNormalUser = true;
+            openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+          };
+        in {
+          # SFTPGo admin user
+          admin = commonAttrs // {
+            password = adminPassword;
+          };
+
+          # Alice and bob share folders with each other
+          alice = commonAttrs // {
+            password = alicePassword;
+            extraGroups = [ sharedFolderName ];
+          };
+
+          bob = commonAttrs // {
+            password = bobPassword;
+            extraGroups = [ sharedFolderName ];
+          };
+
+          # Eve has no shared folders
+          eve = commonAttrs // {
+            password = evePassword;
+          };
+        };
+
+      users.groups.${sharedFolderName} = {};
+
+      specialisation = {
+        # A specialisation for asserting that SFTPGo can bind to privileged ports.
+        privilegedPorts.configuration = { ... }: {
+          networking.firewall.allowedTCPPorts = [ 22 80 ];
+          services.sftpgo = {
+            settings = {
+              sftpd.bindings = lib.mkForce [{
+                address = "";
+                port = 22;
+              }];
+
+              httpd.bindings = lib.mkForce [{
+                address = "";
+                port = 80;
+              }];
+            };
+          };
+        };
+      };
+    };
+
+    client = { nodes, ... }: {
+      # Add the SFTPGo host key to the global known_hosts file
+      programs.ssh.knownHosts =
+        let
+          commonAttrs = {
+            publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE61C7pTXfnLG2u9So+ijNTKaSOg009UrquqNL3fpEu1";
+          };
+        in {
+          "server" = commonAttrs;
+          "[server]:2022" = commonAttrs;
+        };
+      };
+  };
+
+  testScript = { nodes, ... }: let
+    # A function to generate test cases for wheter
+    # a specified username is expected to access the shared folder.
+    accessSharedFoldersSubtest =
+      { # The username to run as
+        username
+        # Whether the tests are expected to succeed or not
+      , shouldSucceed ? true
+      }: ''
+        with subtest("Test whether ${username} can access shared folders"):
+            client.${if shouldSucceed then "succeed" else "fail"}("sftp -P ${toString sftpPort} -b ${
+              pkgs.writeText "${username}-ls-${sharedFolderName}" ''
+                ls ${sharedFolderName}
+              ''
+            } ${username}@server")
+      '';
+      statePath = nodes.server.services.sftpgo.dataDir;
+  in ''
+    start_all()
+
+    client.wait_for_unit("default.target")
+    server.wait_for_unit("sftpgo.service")
+
+    with subtest("web client"):
+        client.wait_until_succeeds("curl -sSf http://server:${toString httpPort}/web/client/login")
+
+        # Ensure sftpgo found the static folder
+        client.wait_until_succeeds("curl -o /dev/null -sSf http://server:${toString httpPort}/static/favicon.ico")
+
+    with subtest("Setup SSH keys"):
+        client.succeed("mkdir -m 700 /root/.ssh")
+        client.succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa")
+        client.succeed("chmod 600 /root/.ssh/id_ecdsa")
+
+    with subtest("Copy a file over sftp"):
+        client.wait_until_succeeds("scp -P ${toString sftpPort} ${toString testFile} alice@server:/private/${testFile.name}")
+        server.succeed("test -s ${statePath}/users/alice/private/${testFile.name}")
+
+        # The configured ACL should prevent uploading files to the root directory
+        client.fail("scp -P ${toString sftpPort} ${toString testFile} alice@server:/")
+
+    with subtest("Attempting an interactive SSH sessions must fail"):
+        client.fail("ssh -p ${toString sftpPort} alice@server")
+
+    ${accessSharedFoldersSubtest {
+      username = "alice";
+      shouldSucceed = true;
+    }}
+
+    ${accessSharedFoldersSubtest {
+      username = "bob";
+      shouldSucceed = true;
+    }}
+
+    ${accessSharedFoldersSubtest {
+      username = "eve";
+      shouldSucceed = false;
+    }}
+
+    with subtest("Test sharing files"):
+        # Alice uploads a file to shared folder
+        client.succeed("scp -P ${toString sftpPort} ${toString sharedFile} alice@server:/${sharedFolderName}/${sharedFile.name}")
+        server.succeed("test -s ${statePath}/${sharedFolderName}/${sharedFile.name}")
+
+        # Bob downloads the file from shared folder
+        client.succeed("scp -P ${toString sftpPort} bob@server:/shared/${sharedFile.name} ${sharedFile.name}")
+        client.succeed("test -s ${sharedFile.name}")
+
+        # Eve should not get the file from shared folder
+        client.fail("scp -P ${toString sftpPort} eve@server:/shared/${sharedFile.name}")
+
+    server.succeed("/run/current-system/specialisation/privilegedPorts/bin/switch-to-configuration test")
+
+    client.wait_until_succeeds("sftp -P 22 -b ${pkgs.writeText "get-hello-world.txt" ''
+      get /private/${testFile.name}
+    ''} alice@server")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/sfxr-qt.nix b/nixpkgs/nixos/tests/sfxr-qt.nix
new file mode 100644
index 000000000000..976b9b11fc66
--- /dev/null
+++ b/nixpkgs/nixos/tests/sfxr-qt.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "sfxr-qt";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    sound.enable = true;
+    environment.systemPackages = [ pkgs.sfxr-qt ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      # Add a dummy sound card, or the program won't start
+      machine.execute("modprobe snd-dummy")
+
+      machine.execute("sfxr-qt >&2 &")
+
+      machine.wait_for_window(r"sfxr")
+      machine.sleep(10)
+      machine.wait_for_text("requency")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/sgt-puzzles.nix b/nixpkgs/nixos/tests/sgt-puzzles.nix
new file mode 100644
index 000000000000..4c5210bfce77
--- /dev/null
+++ b/nixpkgs/nixos/tests/sgt-puzzles.nix
@@ -0,0 +1,34 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+{
+  name = "sgt-puzzles";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tomfitzhenry ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = with pkgs; [
+      sgt-puzzles
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+  ''
+    start_all()
+    machine.wait_for_x()
+
+    machine.execute("mines >&2 &")
+
+    machine.wait_for_window("Mines")
+    machine.wait_for_text("Marked")
+    machine.screenshot("mines")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/shadow.nix b/nixpkgs/nixos/tests/shadow.nix
new file mode 100644
index 000000000000..a027af7e450b
--- /dev/null
+++ b/nixpkgs/nixos/tests/shadow.nix
@@ -0,0 +1,172 @@
+let
+  password1 = "foobar";
+  password2 = "helloworld";
+  password3 = "bazqux";
+  password4 = "asdf123";
+  hashed_bcrypt = "$2b$05$8xIEflrk2RxQtcVXbGIxs.Vl0x7dF1/JSv3cyX6JJt0npzkTCWvxK"; # fnord
+  hashed_yeshash = "$y$j9T$d8Z4EAf8P1SvM/aDFbxMS0$VnTXMp/Hnc7QdCBEaLTq5ZFOAFo2/PM0/xEAFuOE88."; # fnord
+  hashed_sha512crypt = "$6$ymzs8WINZ5wGwQcV$VC2S0cQiX8NVukOLymysTPn4v1zJoJp3NGyhnqyv/dAf4NWZsBWYveQcj6gEJr4ZUjRBRjM0Pj1L8TCQ8hUUp0"; # meow
+in import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "shadow";
+  meta = with pkgs.lib.maintainers; { maintainers = [ nequissimus ]; };
+
+  nodes.shadow = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.shadow ];
+
+    users = {
+      mutableUsers = true;
+      users.emma = {
+        isNormalUser = true;
+        password = password1;
+        shell = pkgs.bash;
+      };
+      users.layla = {
+        isNormalUser = true;
+        password = password2;
+        shell = pkgs.shadow;
+      };
+      users.ash = {
+        isNormalUser = true;
+        password = password4;
+        shell = pkgs.bash;
+      };
+      users.berta = {
+        isNormalUser = true;
+        hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath;
+        shell = pkgs.bash;
+      };
+      users.yesim = {
+        isNormalUser = true;
+        hashedPassword = hashed_yeshash;
+        shell = pkgs.bash;
+      };
+      users.leo = {
+        isNormalUser = true;
+        initialHashedPassword = "!";
+        hashedPassword = hashed_sha512crypt; # should take precedence over initialHashedPassword
+        shell = pkgs.bash;
+      };
+    };
+  };
+
+  testScript = ''
+    shadow.wait_for_unit("multi-user.target")
+    shadow.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+    with subtest("Normal login"):
+        shadow.send_key("alt-f2")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+        shadow.wait_for_unit("getty@tty2.service")
+        shadow.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+        shadow.wait_until_tty_matches("2", "login: ")
+        shadow.send_chars("emma\n")
+        shadow.wait_until_tty_matches("2", "login: emma")
+        shadow.wait_until_succeeds("pgrep login")
+        shadow.sleep(2)
+        shadow.send_chars("${password1}\n")
+        shadow.send_chars("whoami > /tmp/1\n")
+        shadow.wait_for_file("/tmp/1")
+        assert "emma" in shadow.succeed("cat /tmp/1")
+
+    with subtest("Switch user"):
+        shadow.send_chars("su - ash\n")
+        shadow.sleep(2)
+        shadow.send_chars("${password4}\n")
+        shadow.sleep(2)
+        shadow.send_chars("whoami > /tmp/3\n")
+        shadow.wait_for_file("/tmp/3")
+        assert "ash" in shadow.succeed("cat /tmp/3")
+
+    with subtest("Change password"):
+        shadow.send_key("alt-f3")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 3 ]")
+        shadow.wait_for_unit("getty@tty3.service")
+        shadow.wait_until_succeeds("pgrep -f 'agetty.*tty3'")
+        shadow.wait_until_tty_matches("3", "login: ")
+        shadow.send_chars("emma\n")
+        shadow.wait_until_tty_matches("3", "login: emma")
+        shadow.wait_until_succeeds("pgrep login")
+        shadow.sleep(2)
+        shadow.send_chars("${password1}\n")
+        shadow.send_chars("passwd\n")
+        shadow.sleep(2)
+        shadow.send_chars("${password1}\n")
+        shadow.sleep(2)
+        shadow.send_chars("${password3}\n")
+        shadow.sleep(2)
+        shadow.send_chars("${password3}\n")
+        shadow.sleep(2)
+        shadow.send_key("alt-f4")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 4 ]")
+        shadow.wait_for_unit("getty@tty4.service")
+        shadow.wait_until_succeeds("pgrep -f 'agetty.*tty4'")
+        shadow.wait_until_tty_matches("4", "login: ")
+        shadow.send_chars("emma\n")
+        shadow.wait_until_tty_matches("4", "login: emma")
+        shadow.wait_until_succeeds("pgrep login")
+        shadow.sleep(2)
+        shadow.send_chars("${password1}\n")
+        shadow.wait_until_tty_matches("4", "Login incorrect")
+        shadow.wait_until_tty_matches("4", "login:")
+        shadow.send_chars("emma\n")
+        shadow.wait_until_tty_matches("4", "login: emma")
+        shadow.wait_until_succeeds("pgrep login")
+        shadow.sleep(2)
+        shadow.send_chars("${password3}\n")
+        shadow.send_chars("whoami > /tmp/2\n")
+        shadow.wait_for_file("/tmp/2")
+        assert "emma" in shadow.succeed("cat /tmp/2")
+
+    with subtest("Groups"):
+        assert "foobar" not in shadow.succeed("groups emma")
+        shadow.succeed("groupadd foobar")
+        shadow.succeed("usermod -a -G foobar emma")
+        assert "foobar" in shadow.succeed("groups emma")
+
+    with subtest("nologin shell"):
+        shadow.send_key("alt-f5")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 5 ]")
+        shadow.wait_for_unit("getty@tty5.service")
+        shadow.wait_until_succeeds("pgrep -f 'agetty.*tty5'")
+        shadow.wait_until_tty_matches("5", "login: ")
+        shadow.send_chars("layla\n")
+        shadow.wait_until_tty_matches("5", "login: layla")
+        shadow.wait_until_succeeds("pgrep login")
+        shadow.send_chars("${password2}\n")
+        shadow.wait_until_tty_matches("5", "login:")
+
+    with subtest("check alternate password hashes"):
+        shadow.send_key("alt-f6")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 6 ]")
+        for u in ["berta", "yesim"]:
+            shadow.wait_for_unit("getty@tty6.service")
+            shadow.wait_until_succeeds("pgrep -f 'agetty.*tty6'")
+            shadow.wait_until_tty_matches("6", "login: ")
+            shadow.send_chars(f"{u}\n")
+            shadow.wait_until_tty_matches("6", f"login: {u}")
+            shadow.wait_until_succeeds("pgrep login")
+            shadow.sleep(2)
+            shadow.send_chars("fnord\n")
+            shadow.send_chars(f"whoami > /tmp/{u}\n")
+            shadow.wait_for_file(f"/tmp/{u}")
+            print(shadow.succeed(f"cat /tmp/{u}"))
+            assert u in shadow.succeed(f"cat /tmp/{u}")
+            shadow.send_chars("logout\n")
+
+    with subtest("Ensure hashedPassword does not get overridden by initialHashedPassword"):
+        shadow.send_key("alt-f6")
+        shadow.wait_until_succeeds("[ $(fgconsole) = 6 ]")
+        shadow.wait_for_unit("getty@tty6.service")
+        shadow.wait_until_succeeds("pgrep -f 'agetty.*tty6'")
+        shadow.wait_until_tty_matches("6", "login: ")
+        shadow.send_chars("leo\n")
+        shadow.wait_until_tty_matches("6", "login: leo")
+        shadow.wait_until_succeeds("pgrep login")
+        shadow.sleep(2)
+        shadow.send_chars("meow\n")
+        shadow.send_chars("whoami > /tmp/leo\n")
+        shadow.wait_for_file("/tmp/leo")
+        assert "leo" in shadow.succeed("cat /tmp/leo")
+        shadow.send_chars("logout\n")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/shadowsocks/common.nix b/nixpkgs/nixos/tests/shadowsocks/common.nix
new file mode 100644
index 000000000000..82a63771b03a
--- /dev/null
+++ b/nixpkgs/nixos/tests/shadowsocks/common.nix
@@ -0,0 +1,85 @@
+{ name
+, plugin ? null
+, pluginOpts ? ""
+}:
+
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+    inherit name;
+    meta = {
+      maintainers = with lib.maintainers; [ hmenke ];
+    };
+
+    nodes = {
+      server = {
+        boot.kernel.sysctl."net.ipv4.ip_forward" = "1";
+        networking.useDHCP = false;
+        networking.interfaces.eth1.ipv4.addresses = [
+          { address = "192.168.0.1"; prefixLength = 24; }
+        ];
+        networking.firewall.rejectPackets = true;
+        networking.firewall.allowedTCPPorts = [ 8488 ];
+        networking.firewall.allowedUDPPorts = [ 8488 ];
+        services.shadowsocks = {
+          enable = true;
+          encryptionMethod = "chacha20-ietf-poly1305";
+          password = "pa$$w0rd";
+          localAddress = [ "0.0.0.0" ];
+          port = 8488;
+          fastOpen = false;
+          mode = "tcp_and_udp";
+        } // lib.optionalAttrs (plugin != null) {
+          inherit plugin;
+          pluginOpts = "server;${pluginOpts}";
+        };
+        services.nginx = {
+          enable = true;
+          virtualHosts.server = {
+            locations."/".root = pkgs.writeTextDir "index.html" "It works!";
+          };
+        };
+      };
+
+      client = {
+        networking.useDHCP = false;
+        networking.interfaces.eth1.ipv4.addresses = [
+          { address = "192.168.0.2"; prefixLength = 24; }
+        ];
+        systemd.services.shadowsocks-client = {
+          description = "connect to shadowsocks";
+          after = [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+          path = with pkgs; [ shadowsocks-libev ];
+          script = ''
+            exec ss-local \
+                -s 192.168.0.1 \
+                -p 8488 \
+                -l 1080 \
+                -k 'pa$$w0rd' \
+                -m chacha20-ietf-poly1305 \
+                -a nobody \
+                ${lib.optionalString (plugin != null) ''
+                  --plugin "${plugin}" --plugin-opts "${pluginOpts}"
+                ''}
+          '';
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      server.wait_for_unit("shadowsocks-libev.service")
+      server.wait_for_unit("nginx.service")
+      client.wait_for_unit("shadowsocks-client.service")
+
+      client.fail(
+          "${pkgs.curl}/bin/curl 192.168.0.1:80"
+      )
+
+      msg = client.succeed(
+          "${pkgs.curl}/bin/curl --socks5 localhost:1080 192.168.0.1:80"
+      )
+      assert msg == "It works!", "Could not connect through shadowsocks"
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/shadowsocks/default.nix b/nixpkgs/nixos/tests/shadowsocks/default.nix
new file mode 100644
index 000000000000..37a8c3c9d0d3
--- /dev/null
+++ b/nixpkgs/nixos/tests/shadowsocks/default.nix
@@ -0,0 +1,16 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
+
+{
+  "basic" = import ./common.nix {
+    name = "basic";
+  };
+
+  "v2ray-plugin" = import ./common.nix {
+    name = "v2ray-plugin";
+    plugin = "${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin";
+    pluginOpts = "host=nixos.org";
+  };
+}
diff --git a/nixpkgs/nixos/tests/shattered-pixel-dungeon.nix b/nixpkgs/nixos/tests/shattered-pixel-dungeon.nix
new file mode 100644
index 000000000000..b4ac1670b5ca
--- /dev/null
+++ b/nixpkgs/nixos/tests/shattered-pixel-dungeon.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "shattered-pixel-dungeon";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    sound.enable = true;
+    environment.systemPackages = [ pkgs.shattered-pixel-dungeon ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("shattered-pixel-dungeon >&2 &")
+      machine.wait_for_window(r"Shattered Pixel Dungeon")
+      machine.wait_for_text("Enter")
+      machine.screenshot("screen")
+    '';
+})
+
diff --git a/nixpkgs/nixos/tests/shiori.nix b/nixpkgs/nixos/tests/shiori.nix
new file mode 100644
index 000000000000..d0f68b903f8c
--- /dev/null
+++ b/nixpkgs/nixos/tests/shiori.nix
@@ -0,0 +1,80 @@
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+
+{
+  name = "shiori";
+  meta.maintainers = with lib.maintainers; [ minijackson ];
+
+  nodes.machine =
+    { ... }:
+    { services.shiori.enable = true; };
+
+  testScript = let
+    authJSON = pkgs.writeText "auth.json" (builtins.toJSON {
+      username = "shiori";
+      password = "gopher";
+      owner = true;
+    });
+
+  insertBookmark = {
+    url = "http://example.org";
+    title = "Example Bookmark";
+  };
+
+  insertBookmarkJSON = pkgs.writeText "insertBookmark.json" (builtins.toJSON insertBookmark);
+  in ''
+    import json
+
+    machine.wait_for_unit("shiori.service")
+    machine.wait_for_open_port(8080)
+    machine.succeed("curl --fail http://localhost:8080/")
+    machine.succeed("curl --fail --location http://localhost:8080/ | grep -i shiori")
+
+    with subtest("login"):
+        auth_json = machine.succeed(
+            "curl --fail --location http://localhost:8080/api/login "
+            "-X POST -H 'Content-Type:application/json' -d @${authJSON}"
+        )
+        auth_ret = json.loads(auth_json)
+        session_id = auth_ret["session"]
+
+    with subtest("bookmarks"):
+        with subtest("first use no bookmarks"):
+            bookmarks_json = machine.succeed(
+                (
+                    "curl --fail --location http://localhost:8080/api/bookmarks "
+                    "-H 'X-Session-Id:{}'"
+                ).format(session_id)
+            )
+
+            if json.loads(bookmarks_json)["bookmarks"] != []:
+                raise Exception("Shiori have a bookmark on first use")
+
+        with subtest("insert bookmark"):
+            machine.succeed(
+                (
+                    "curl --fail --location http://localhost:8080/api/bookmarks "
+                    "-X POST -H 'X-Session-Id:{}' "
+                    "-H 'Content-Type:application/json' -d @${insertBookmarkJSON}"
+                ).format(session_id)
+            )
+
+        with subtest("get inserted bookmark"):
+            bookmarks_json = machine.succeed(
+                (
+                    "curl --fail --location http://localhost:8080/api/bookmarks "
+                    "-H 'X-Session-Id:{}'"
+                ).format(session_id)
+            )
+
+            bookmarks = json.loads(bookmarks_json)["bookmarks"]
+            if len(bookmarks) != 1:
+                raise Exception("Shiori didn't save the bookmark")
+
+            bookmark = bookmarks[0]
+            if (
+                bookmark["url"] != "${insertBookmark.url}"
+                or bookmark["title"] != "${insertBookmark.title}"
+            ):
+                raise Exception("Inserted bookmark doesn't have same URL or title")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/signal-desktop.nix b/nixpkgs/nixos/tests/signal-desktop.nix
new file mode 100644
index 000000000000..f146804a958d
--- /dev/null
+++ b/nixpkgs/nixos/tests/signal-desktop.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  sqlcipher-signal = pkgs.writeShellScriptBin "sqlcipher" ''
+    set -eu
+
+    readonly CFG=~/.config/Signal/config.json
+    readonly KEY="$(${pkgs.jq}/bin/jq --raw-output '.key' $CFG)"
+    readonly DB="$1"
+    readonly SQL="SELECT * FROM sqlite_master where type='table'"
+    ${pkgs.sqlcipher}/bin/sqlcipher "$DB" "PRAGMA key = \"x'$KEY'\"; $SQL"
+  '';
+in {
+  name = "signal-desktop";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ flokli primeos ];
+  };
+
+  nodes.machine = { ... }:
+
+  {
+    imports = [
+      ./common/user-account.nix
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    test-support.displayManager.auto.user = "alice";
+    environment.systemPackages = with pkgs; [
+      signal-desktop file sqlite sqlcipher-signal
+    ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+  in ''
+    start_all()
+    machine.wait_for_x()
+
+    # start signal desktop
+    machine.execute("su - alice -c signal-desktop >&2 &")
+
+    # Wait for the Signal window to appear. Since usually the tests
+    # are run sandboxed and therefore with no internet, we can not wait
+    # for the message "Link your phone ...". Nor should we wait for
+    # the "Failed to connect to server" message, because when manually
+    # running this test it will be not sandboxed.
+    machine.wait_for_text("Signal")
+    machine.wait_for_text("File Edit View Window Help")
+    machine.screenshot("signal_desktop")
+
+    # Test if the database is encrypted to prevent these issues:
+    # - https://github.com/NixOS/nixpkgs/issues/108772
+    # - https://github.com/NixOS/nixpkgs/pull/117555
+    print(machine.succeed("su - alice -c 'file ~/.config/Signal/sql/db.sqlite'"))
+    machine.fail(
+        "su - alice -c 'file ~/.config/Signal/sql/db.sqlite' | grep -e SQLite -e database"
+    )
+    # Only SQLCipher should be able to read the encrypted DB:
+    machine.fail(
+        "su - alice -c 'sqlite3 ~/.config/Signal/sql/db.sqlite .tables'"
+    )
+    print(machine.succeed(
+        "su - alice -c 'sqlcipher ~/.config/Signal/sql/db.sqlite'"
+    ))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/simple.nix b/nixpkgs/nixos/tests/simple.nix
new file mode 100644
index 000000000000..c36287b4e843
--- /dev/null
+++ b/nixpkgs/nixos/tests/simple.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "simple";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.shutdown()
+    '';
+})
diff --git a/nixpkgs/nixos/tests/sing-box.nix b/nixpkgs/nixos/tests/sing-box.nix
new file mode 100644
index 000000000000..582d594be3fd
--- /dev/null
+++ b/nixpkgs/nixos/tests/sing-box.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+
+  name = "sing-box";
+
+  meta = {
+    maintainers = with lib.maintainers; [ nickcao ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.nginx = {
+      enable = true;
+      statusPage = true;
+    };
+    services.sing-box = {
+      enable = true;
+      settings = {
+        inbounds = [{
+          type = "mixed";
+          tag = "inbound";
+          listen = "127.0.0.1";
+          listen_port = 1080;
+          users = [{
+            username = "user";
+            password = { _secret = pkgs.writeText "password" "supersecret"; };
+          }];
+        }];
+        outbounds = [{
+          type = "direct";
+          tag = "outbound";
+        }];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_unit("sing-box.service")
+
+    machine.wait_for_open_port(80)
+    machine.wait_for_open_port(1080)
+
+    machine.succeed("curl --fail --max-time 10 --proxy http://user:supersecret@localhost:1080 http://localhost")
+    machine.fail("curl --fail --max-time 10 --proxy http://user:supervillain@localhost:1080 http://localhost")
+    machine.succeed("curl --fail --max-time 10 --proxy socks5://user:supersecret@localhost:1080 http://localhost")
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/slimserver.nix b/nixpkgs/nixos/tests/slimserver.nix
new file mode 100644
index 000000000000..95cbdcf4a2a1
--- /dev/null
+++ b/nixpkgs/nixos/tests/slimserver.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "slimserver";
+  meta.maintainers = with pkgs.lib.maintainers; [ adamcstephens ];
+
+  nodes.machine = { ... }: {
+    services.slimserver.enable = true;
+    services.squeezelite = {
+      enable = true;
+      extraArguments = "-s 127.0.0.1 -d slimproto=info";
+    };
+    sound.enable = true;
+    boot.initrd.kernelModules = ["snd-dummy"];
+  };
+
+  testScript =
+    ''
+      import json
+      rpc_get_player = {
+          "id": 1,
+          "method": "slim.request",
+          "params":[0,["player", "id", "0", "?"]]
+      }
+
+      with subtest("slimserver is started"):
+          machine.wait_for_unit("slimserver.service")
+          # give slimserver a moment to report errors
+          machine.sleep(2)
+
+      with subtest('slimserver module errors are not reported'):
+          machine.fail("journalctl -u slimserver.service | grep 'throw_exception'")
+          machine.fail("journalctl -u slimserver.service | grep 'not installed'")
+          machine.fail("journalctl -u slimserver.service | grep 'not found'")
+          machine.fail("journalctl -u slimserver.service | grep 'The following CPAN modules were found but cannot work with Logitech Media Server'")
+          machine.fail("journalctl -u slimserver.service | grep 'please use the buildme.sh'")
+
+      with subtest('slimserver is ready'):
+          machine.wait_for_open_port(9000)
+          machine.wait_until_succeeds("journalctl -u slimserver.service | grep 'Completed dbOptimize Scan'")
+
+      with subtest("squeezelite player successfully connects to slimserver"):
+          machine.wait_for_unit("squeezelite.service")
+          machine.wait_until_succeeds("journalctl -u squeezelite.service | grep -E 'slimproto:[0-9]+ connected'")
+          player_mac = machine.wait_until_succeeds("journalctl -eu squeezelite.service | grep -E 'sendHELO:[0-9]+ mac:'").strip().split(" ")[-1]
+          player_id = machine.succeed(f"curl http://localhost:9000/jsonrpc.js -g -X POST -d '{json.dumps(rpc_get_player)}'")
+          assert player_mac == json.loads(player_id)["result"]["_id"], "squeezelite player not found"
+    '';
+})
diff --git a/nixpkgs/nixos/tests/slurm.nix b/nixpkgs/nixos/tests/slurm.nix
new file mode 100644
index 000000000000..ad516b6e8d2b
--- /dev/null
+++ b/nixpkgs/nixos/tests/slurm.nix
@@ -0,0 +1,168 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+let
+    slurmconfig = {
+      services.slurm = {
+        controlMachine = "control";
+        nodeName = [ "node[1-3] CPUs=1 State=UNKNOWN" ];
+        partitionName = [ "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP" ];
+        extraConfig = ''
+          AccountingStorageHost=dbd
+          AccountingStorageType=accounting_storage/slurmdbd
+        '';
+      };
+      environment.systemPackages = [ mpitest ];
+      networking.firewall.enable = false;
+      systemd.tmpfiles.rules = [
+        "f /etc/munge/munge.key 0400 munge munge - mungeverryweakkeybuteasytointegratoinatest"
+      ];
+    };
+
+    mpitest = let
+      mpitestC = pkgs.writeText "mpitest.c" ''
+        #include <stdio.h>
+        #include <stdlib.h>
+        #include <mpi.h>
+
+        int
+        main (int argc, char *argv[])
+        {
+          int rank, size, length;
+          char name[512];
+
+          MPI_Init (&argc, &argv);
+          MPI_Comm_rank (MPI_COMM_WORLD, &rank);
+          MPI_Comm_size (MPI_COMM_WORLD, &size);
+          MPI_Get_processor_name (name, &length);
+
+          if ( rank == 0 ) printf("size=%d\n", size);
+
+          printf ("%s: hello world from process %d of %d\n", name, rank, size);
+
+          MPI_Finalize ();
+
+          return EXIT_SUCCESS;
+        }
+      '';
+    in pkgs.runCommand "mpitest" {} ''
+      mkdir -p $out/bin
+      ${lib.getDev pkgs.mpi}/bin/mpicc ${mpitestC} -o $out/bin/mpitest
+    '';
+in {
+  name = "slurm";
+
+  meta.maintainers = [ lib.maintainers.markuskowa ];
+
+  nodes =
+    let
+    computeNode =
+      { ...}:
+      {
+        imports = [ slurmconfig ];
+        # TODO slurmd port and slurmctld port should be configurations and
+        # automatically allowed by the  firewall.
+        services.slurm = {
+          client.enable = true;
+        };
+      };
+    in {
+
+    control =
+      { ...}:
+      {
+        imports = [ slurmconfig ];
+        services.slurm = {
+          server.enable = true;
+        };
+      };
+
+    submit =
+      { ...}:
+      {
+        imports = [ slurmconfig ];
+        services.slurm = {
+          enableStools = true;
+        };
+      };
+
+    dbd =
+      { pkgs, ... } :
+      let
+        passFile = pkgs.writeText "dbdpassword" "password123";
+      in {
+        networking.firewall.enable = false;
+        systemd.tmpfiles.rules = [
+          "f /etc/munge/munge.key 0400 munge munge - mungeverryweakkeybuteasytointegratoinatest"
+        ];
+        services.slurm.dbdserver = {
+          enable = true;
+          storagePassFile = "${passFile}";
+        };
+        services.mysql = {
+          enable = true;
+          package = pkgs.mariadb;
+          initialScript = pkgs.writeText "mysql-init.sql" ''
+            CREATE USER 'slurm'@'localhost' IDENTIFIED BY 'password123';
+            GRANT ALL PRIVILEGES ON slurm_acct_db.* TO 'slurm'@'localhost';
+          '';
+          ensureDatabases = [ "slurm_acct_db" ];
+          ensureUsers = [{
+            ensurePermissions = { "slurm_acct_db.*" = "ALL PRIVILEGES"; };
+            name = "slurm";
+          }];
+          settings.mysqld = {
+            # recommendations from: https://slurm.schedmd.com/accounting.html#mysql-configuration
+            innodb_buffer_pool_size="1024M";
+            innodb_log_file_size="64M";
+            innodb_lock_wait_timeout=900;
+          };
+        };
+      };
+
+    node1 = computeNode;
+    node2 = computeNode;
+    node3 = computeNode;
+  };
+
+
+  testScript =
+  ''
+  start_all()
+
+  # Make sure DBD is up after DB initialzation
+  with subtest("can_start_slurmdbd"):
+      dbd.succeed("systemctl restart slurmdbd")
+      dbd.wait_for_unit("slurmdbd.service")
+      dbd.wait_for_open_port(6819)
+
+  # there needs to be an entry for the current
+  # cluster in the database before slurmctld is restarted
+  with subtest("add_account"):
+      control.succeed("sacctmgr -i add cluster default")
+      # check for cluster entry
+      control.succeed("sacctmgr list cluster | awk '{ print $1 }' | grep default")
+
+  with subtest("can_start_slurmctld"):
+      control.succeed("systemctl restart slurmctld")
+      control.wait_for_unit("slurmctld.service")
+
+  with subtest("can_start_slurmd"):
+      for node in [node1, node2, node3]:
+          node.succeed("systemctl restart slurmd.service")
+          node.wait_for_unit("slurmd")
+
+  # Test that the cluster works and can distribute jobs;
+
+  with subtest("run_distributed_command"):
+      # Run `hostname` on 3 nodes of the partition (so on all the 3 nodes).
+      # The output must contain the 3 different names
+      submit.succeed("srun -N 3 hostname | sort | uniq | wc -l | xargs test 3 -eq")
+
+      with subtest("check_slurm_dbd"):
+          # find the srun job from above in the database
+          control.succeed("sleep 5")
+          control.succeed("sacct | grep hostname")
+
+  with subtest("run_PMIx_mpitest"):
+      submit.succeed("srun -N 3 --mpi=pmix mpitest | grep size=3")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/smokeping.nix b/nixpkgs/nixos/tests/smokeping.nix
new file mode 100644
index 000000000000..04f813964291
--- /dev/null
+++ b/nixpkgs/nixos/tests/smokeping.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "smokeping";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ cransom ];
+  };
+
+  nodes = {
+    sm =
+      { ... }:
+      {
+        networking.domain = "example.com"; # FQDN: sm.example.com
+        services.smokeping = {
+          enable = true;
+          port = 8081;
+          mailHost = "127.0.0.2";
+          probeConfig = ''
+            + FPing
+            binary = /run/wrappers/bin/fping
+            offset = 0%
+          '';
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    sm.wait_for_unit("smokeping")
+    sm.wait_for_unit("thttpd")
+    sm.wait_for_file("/var/lib/smokeping/data/Local/LocalMachine.rrd")
+    sm.succeed("curl -s -f localhost:8081/smokeping.fcgi?target=Local")
+    # Check that there's a helpful page without explicit path as well.
+    sm.succeed("curl -s -f localhost:8081")
+    sm.succeed("ls /var/lib/smokeping/cache/Local/LocalMachine_mini.png")
+    sm.succeed("ls /var/lib/smokeping/cache/index.html")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/snapcast.nix b/nixpkgs/nixos/tests/snapcast.nix
new file mode 100644
index 000000000000..9b62e4724e75
--- /dev/null
+++ b/nixpkgs/nixos/tests/snapcast.nix
@@ -0,0 +1,90 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  port = 10004;
+  tcpPort = 10005;
+  httpPort = 10080;
+  tcpStreamPort = 10006;
+  bufferSize = 742;
+in {
+  name = "snapcast";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hexa ];
+  };
+
+  nodes = {
+    server = {
+      services.snapserver = {
+        enable = true;
+        port = port;
+        tcp.port = tcpPort;
+        http.port = httpPort;
+        openFirewall = true;
+        buffer = bufferSize;
+        streams = {
+          mpd = {
+            type = "pipe";
+            location = "/run/snapserver/mpd";
+            query.mode = "create";
+          };
+          bluetooth = {
+            type = "pipe";
+            location = "/run/snapserver/bluetooth";
+          };
+          tcp = {
+            type = "tcp";
+            location = "127.0.0.1:${toString tcpStreamPort}";
+          };
+          meta = {
+            type = "meta";
+            location = "/mpd/bluetooth/tcp";
+          };
+        };
+      };
+      environment.systemPackages = [ pkgs.snapcast ];
+    };
+    client = {
+      environment.systemPackages = [ pkgs.snapcast ];
+    };
+  };
+
+  testScript = ''
+    import json
+
+    get_rpc_version = {"id": "1", "jsonrpc": "2.0", "method": "Server.GetRPCVersion"}
+
+    start_all()
+
+    server.wait_for_unit("snapserver.service")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString port}")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString tcpPort}")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString httpPort}")
+    server.wait_until_succeeds("ss -ntl | grep -q ${toString tcpStreamPort}")
+
+    with subtest("check that pipes are created"):
+        server.succeed("test -p /run/snapserver/mpd")
+        server.succeed("test -p /run/snapserver/bluetooth")
+
+    with subtest("test tcp json-rpc"):
+        server.succeed(f"echo '{json.dumps(get_rpc_version)}' | nc -w 1 localhost ${toString tcpPort}")
+
+    with subtest("test http json-rpc"):
+        server.succeed(
+            "curl --fail http://localhost:${toString httpPort}/jsonrpc -d '{json.dumps(get_rpc_version)}'"
+        )
+
+    with subtest("test a ipv6 connection"):
+        server.execute("systemd-run --unit=snapcast-local-client snapclient -h ::1 -p ${toString port}")
+        server.wait_until_succeeds(
+            "journalctl -o cat -u snapserver.service | grep -q 'Hello from'"
+        )
+        server.wait_until_succeeds("journalctl -o cat -u snapcast-local-client | grep -q 'buffer: ${toString bufferSize}'")
+
+    with subtest("test a connection"):
+        client.execute("systemd-run --unit=snapcast-client snapclient -h server -p ${toString port}")
+        server.wait_until_succeeds(
+            "journalctl -o cat -u snapserver.service | grep -q 'Hello from'"
+        )
+        client.wait_until_succeeds("journalctl -o cat -u snapcast-client | grep -q 'buffer: ${toString bufferSize}'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/snapper.nix b/nixpkgs/nixos/tests/snapper.nix
new file mode 100644
index 000000000000..674523584fda
--- /dev/null
+++ b/nixpkgs/nixos/tests/snapper.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ ... }:
+{
+  name = "snapper";
+
+  nodes.machine = { pkgs, lib, ... }: {
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux /dev/vdb
+    '';
+
+    virtualisation.emptyDiskImages = [ 4096 ];
+
+    virtualisation.fileSystems = {
+      "/home" = {
+        device = "/dev/disk/by-label/aux";
+        fsType = "btrfs";
+      };
+    };
+    services.snapper.configs.home.SUBVOLUME = "/home";
+    services.snapper.filters = "/nix";
+  };
+
+  testScript = ''
+    machine.succeed("btrfs subvolume create /home/.snapshots")
+    machine.succeed("snapper -c home list")
+    machine.succeed("snapper -c home create --description empty")
+    machine.succeed("echo test > /home/file")
+    machine.succeed("snapper -c home create --description file")
+    machine.succeed("snapper -c home status 1..2")
+    machine.succeed("snapper -c home undochange 1..2")
+    machine.fail("ls /home/file")
+    machine.succeed("snapper -c home delete 2")
+    machine.succeed("systemctl --wait start snapper-timeline.service")
+    machine.succeed("systemctl --wait start snapper-cleanup.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/snmpd.nix b/nixpkgs/nixos/tests/snmpd.nix
new file mode 100644
index 000000000000..9248a6b39010
--- /dev/null
+++ b/nixpkgs/nixos/tests/snmpd.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "snmpd";
+
+  nodes.snmpd = {
+    environment.systemPackages = with pkgs; [
+      net-snmp
+    ];
+
+    services.snmpd = {
+      enable = true;
+      configText = ''
+        rocommunity public
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all();
+    machine.wait_for_unit("snmpd.service")
+    machine.succeed("snmpwalk -v 2c -c public localhost | grep SNMPv2-MIB::sysName.0");
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/soapui.nix b/nixpkgs/nixos/tests/soapui.nix
new file mode 100644
index 000000000000..3a2d11a16756
--- /dev/null
+++ b/nixpkgs/nixos/tests/soapui.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "soapui";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+
+    environment.systemPackages = [ pkgs.soapui ];
+  };
+
+  testScript = ''
+    machine.wait_for_x()
+    machine.succeed("soapui >&2 &")
+    machine.wait_for_window(r"SoapUI \d+\.\d+\.\d+")
+    machine.sleep(1)
+    machine.screenshot("soapui")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/soft-serve.nix b/nixpkgs/nixos/tests/soft-serve.nix
new file mode 100644
index 000000000000..1c4cb4c95819
--- /dev/null
+++ b/nixpkgs/nixos/tests/soft-serve.nix
@@ -0,0 +1,102 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+  sshPort = 8231;
+  httpPort = 8232;
+  statsPort = 8233;
+  gitPort = 8418;
+in
+{
+  name = "soft-serve";
+  meta.maintainers = with lib.maintainers; [ dadada ];
+  nodes = {
+    client = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [
+        curl
+        git
+        openssh
+      ];
+      environment.etc.sshKey = {
+        source = snakeOilPrivateKey;
+        mode = "0600";
+      };
+    };
+
+    server =
+      { config, ... }:
+      {
+        services.soft-serve = {
+          enable = true;
+          settings = {
+            name = "TestServer";
+            ssh.listen_addr = ":${toString sshPort}";
+            git.listen_addr = ":${toString gitPort}";
+            http.listen_addr = ":${toString httpPort}";
+            stats.listen_addr = ":${toString statsPort}";
+            initial_admin_keys = [ snakeOilPublicKey ];
+          };
+        };
+        networking.firewall.allowedTCPPorts = [ sshPort httpPort statsPort ];
+      };
+  };
+
+  testScript =
+    { ... }:
+    ''
+      SSH_PORT = ${toString sshPort}
+      HTTP_PORT = ${toString httpPort}
+      STATS_PORT = ${toString statsPort}
+      KEY = "${snakeOilPublicKey}"
+      SSH_KEY = "/etc/sshKey"
+      SSH_COMMAND = f"ssh -p {SSH_PORT} -i {SSH_KEY} -o StrictHostKeyChecking=no"
+      TEST_DIR = "/tmp/test"
+      GIT = f"git -C {TEST_DIR}"
+
+      for machine in client, server:
+          machine.wait_for_unit("network.target")
+
+      server.wait_for_unit("soft-serve.service")
+      server.wait_for_open_port(SSH_PORT)
+
+      with subtest("Get info"):
+          status, test = client.execute(f"{SSH_COMMAND} server info")
+          if status != 0:
+              raise Exception("Failed to get SSH info")
+          key = " ".join(KEY.split(" ")[0:2])
+          if not key in test:
+              raise Exception("Admin key must be configured correctly")
+
+      with subtest("Create user"):
+          client.succeed(f"{SSH_COMMAND} server user create beatrice")
+          client.succeed(f"{SSH_COMMAND} server user info beatrice")
+
+      with subtest("Create repo"):
+          client.succeed(f"git init {TEST_DIR}")
+          client.succeed(f"{GIT} config --global user.email you@example.com")
+          client.succeed(f"touch {TEST_DIR}/foo")
+          client.succeed(f"{GIT} add foo")
+          client.succeed(f"{GIT} commit --allow-empty -m test")
+          client.succeed(f"{GIT} remote add origin git@server:test")
+          client.succeed(f"GIT_SSH_COMMAND='{SSH_COMMAND}' {GIT} push -u origin master")
+          client.execute("rm -r /tmp/test")
+
+      server.wait_for_open_port(HTTP_PORT)
+
+      with subtest("Clone over HTTP"):
+          client.succeed(f"curl --connect-timeout 10 http://server:{HTTP_PORT}/")
+          client.succeed(f"git clone http://server:{HTTP_PORT}/test /tmp/test")
+          client.execute("rm -r /tmp/test")
+
+      with subtest("Clone over SSH"):
+          client.succeed(f"GIT_SSH_COMMAND='{SSH_COMMAND}' git clone git@server:test /tmp/test")
+          client.execute("rm -r /tmp/test")
+
+      with subtest("Get stats over HTTP"):
+          server.wait_for_open_port(STATS_PORT)
+          status, test = client.execute(f"curl --connect-timeout 10 http://server:{STATS_PORT}/metrics")
+          if status != 0:
+              raise Exception("Failed to get metrics from status port")
+          if not "go_gc_duration_seconds_count" in test:
+              raise Exception("Metrics did not contain key 'go_gc_duration_seconds_count'")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/sogo.nix b/nixpkgs/nixos/tests/sogo.nix
new file mode 100644
index 000000000000..e9059a2ab773
--- /dev/null
+++ b/nixpkgs/nixos/tests/sogo.nix
@@ -0,0 +1,58 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "sogo";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [];
+  };
+
+  nodes = {
+    sogo = { config, pkgs, ... }: {
+      services.nginx.enable = true;
+
+      services.mysql = {
+        enable = true;
+        package = pkgs.mariadb;
+        ensureDatabases = [ "sogo" ];
+        ensureUsers = [{
+          name = "sogo";
+          ensurePermissions = {
+            "sogo.*" = "ALL PRIVILEGES";
+          };
+        }];
+      };
+
+      services.sogo = {
+        enable = true;
+        timezone = "Europe/Berlin";
+        extraConfig = ''
+          WOWorkersCount = 1;
+
+          SOGoUserSources = (
+            {
+              type = sql;
+              userPasswordAlgorithm = md5;
+              viewURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_users";
+              canAuthenticate = YES;
+              id = users;
+            }
+          );
+
+          SOGoProfileURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_user_profile";
+          OCSFolderInfoURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_folder_info";
+          OCSSessionsFolderURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_sessions_folder";
+          OCSEMailAlarmsFolderURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_alarms_folder";
+          OCSStoreURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_store";
+          OCSAclURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_acl";
+          OCSCacheFolderURL = "mysql://sogo@%2Frun%2Fmysqld%2Fmysqld.sock/sogo/sogo_cache_folder";
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    sogo.wait_for_unit("multi-user.target")
+    sogo.wait_for_open_port(20000)
+    sogo.wait_for_open_port(80)
+    sogo.succeed("curl -sSfL http://sogo/SOGo")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/solanum.nix b/nixpkgs/nixos/tests/solanum.nix
new file mode 100644
index 000000000000..1ecf91bce40b
--- /dev/null
+++ b/nixpkgs/nixos/tests/solanum.nix
@@ -0,0 +1,97 @@
+let
+  clients = [
+    "ircclient1"
+    "ircclient2"
+  ];
+  server = "solanum";
+  ircPort = 6667;
+  channel = "nixos-cat";
+  iiDir = "/tmp/irc";
+in
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "solanum";
+  nodes = {
+    "${server}" = {
+      networking.firewall.allowedTCPPorts = [ ircPort ];
+      services.solanum = {
+        enable = true;
+        motd = ''
+          The default MOTD doesn't contain the word "nixos" in it.
+          This one does.
+        '';
+      };
+    };
+  } // lib.listToAttrs (builtins.map (client: lib.nameValuePair client {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    systemd.services.ii = {
+      requires = [ "network.target" ];
+      wantedBy = [ "default.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        ExecPreStartPre = "mkdir -p ${iiDir}";
+        ExecStart = ''
+          ${lib.getBin pkgs.ii}/bin/ii -n ${client} -s ${server} -i ${iiDir}
+        '';
+        User = "alice";
+      };
+    };
+  }) clients);
+
+  testScript =
+    let
+      msg = client: "Hello, my name is ${client}";
+      clientScript = client: [
+        ''
+          ${client}.wait_for_unit("network.target")
+          ${client}.systemctl("start ii")
+          ${client}.wait_for_unit("ii")
+          ${client}.wait_for_file("${iiDir}/${server}/out")
+        ''
+        # look for the custom text in the MOTD.
+        ''
+          ${client}.wait_until_succeeds("grep 'nixos' ${iiDir}/${server}/out")
+        ''
+        # wait until first PING from server arrives before joining,
+        # so we don't try it too early
+        ''
+          ${client}.wait_until_succeeds("grep 'PING' ${iiDir}/${server}/out")
+        ''
+        # join ${channel}
+        ''
+          ${client}.succeed("echo '/j #${channel}' > ${iiDir}/${server}/in")
+          ${client}.wait_for_file("${iiDir}/${server}/#${channel}/in")
+        ''
+        # send a greeting
+        ''
+          ${client}.succeed(
+              "echo '${msg client}' > ${iiDir}/${server}/#${channel}/in"
+          )
+        ''
+        # check that all greetings arrived on all clients
+      ] ++ builtins.map (other: ''
+        ${client}.succeed(
+            "grep '${msg other}$' ${iiDir}/${server}/#${channel}/out"
+        )
+      '') clients;
+
+      # foldl', but requires a non-empty list instead of a start value
+      reduce = f: list:
+        builtins.foldl' f (builtins.head list) (builtins.tail list);
+    in ''
+      start_all()
+      ${server}.systemctl("status solanum")
+      ${server}.wait_for_open_port(${toString ircPort})
+
+      # run clientScript for all clients so that every list
+      # entry is executed by every client before advancing
+      # to the next one.
+    '' + lib.concatStrings
+      (reduce
+        (lib.zipListsWith (cs: c: cs + c))
+        (builtins.map clientScript clients));
+})
diff --git a/nixpkgs/nixos/tests/sonarr.nix b/nixpkgs/nixos/tests/sonarr.nix
new file mode 100644
index 000000000000..57e6b72db3a3
--- /dev/null
+++ b/nixpkgs/nixos/tests/sonarr.nix
@@ -0,0 +1,16 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "sonarr";
+  meta.maintainers = with lib.maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.sonarr.enable = true; };
+
+  testScript = ''
+    machine.wait_for_unit("sonarr.service")
+    machine.wait_for_open_port(8989)
+    machine.succeed("curl --fail http://localhost:8989/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sonic-server.nix b/nixpkgs/nixos/tests/sonic-server.nix
new file mode 100644
index 000000000000..bb98047619b2
--- /dev/null
+++ b/nixpkgs/nixos/tests/sonic-server.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "sonic-server";
+
+  meta = {
+    maintainers = with lib.maintainers; [ anthonyroussel ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    services.sonic-server.enable = true;
+  };
+
+  testScript = ''
+    machine.start()
+
+    machine.wait_for_unit("sonic-server.service")
+    machine.wait_for_open_port(1491)
+
+    with subtest("Check control mode"):
+      result = machine.succeed('(echo START control; sleep 1; echo PING; echo QUIT) | nc localhost 1491').splitlines()
+      assert result[2] == "PONG", f"expected 'PONG', got '{result[2]}'"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sourcehut/builds.nix b/nixpkgs/nixos/tests/sourcehut/builds.nix
new file mode 100644
index 000000000000..f1f928ecc3d0
--- /dev/null
+++ b/nixpkgs/nixos/tests/sourcehut/builds.nix
@@ -0,0 +1,54 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+let
+  domain = "sourcehut.localdomain";
+in
+{
+  name = "sourcehut";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ tomberek nessdoor ];
+
+  nodes.machine = { config, pkgs, nodes, ... }: {
+    imports = [
+      ./nodes/common.nix
+    ];
+
+    networking.domain = domain;
+    networking.extraHosts = ''
+      ${config.networking.primaryIPAddress} builds.${domain}
+      ${config.networking.primaryIPAddress} meta.${domain}
+    '';
+
+    services.sourcehut = {
+      builds = {
+        enable = true;
+        # FIXME: see why it does not seem to activate fully.
+        #enableWorker = true;
+        images = { };
+      };
+
+      settings."builds.sr.ht" = {
+        oauth-client-secret = pkgs.writeText "buildsrht-oauth-client-secret" "2260e9c4d9b8dcedcef642860e0504bc";
+        oauth-client-id = "299db9f9c2013170";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Check whether meta comes up"):
+         machine.wait_for_unit("metasrht-api.service")
+         machine.wait_for_unit("metasrht.service")
+         machine.wait_for_unit("metasrht-webhooks.service")
+         machine.wait_for_open_port(5000)
+         machine.succeed("curl -sL http://localhost:5000 | grep meta.${domain}")
+         machine.succeed("curl -sL http://meta.${domain} | grep meta.${domain}")
+
+    with subtest("Check whether builds comes up"):
+         machine.wait_for_unit("buildsrht.service")
+         machine.wait_for_open_port(5002)
+         machine.succeed("curl -sL http://localhost:5002 | grep builds.${domain}")
+         #machine.wait_for_unit("buildsrht-worker.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sourcehut/default.nix b/nixpkgs/nixos/tests/sourcehut/default.nix
new file mode 100644
index 000000000000..04f1551d70d9
--- /dev/null
+++ b/nixpkgs/nixos/tests/sourcehut/default.nix
@@ -0,0 +1,6 @@
+{ system, pkgs, ... }:
+
+{
+  git = import ./git.nix { inherit system pkgs; };
+  builds = import ./builds.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/sourcehut/git.nix b/nixpkgs/nixos/tests/sourcehut/git.nix
new file mode 100644
index 000000000000..ed184d5d5518
--- /dev/null
+++ b/nixpkgs/nixos/tests/sourcehut/git.nix
@@ -0,0 +1,96 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+let
+  domain = "sourcehut.localdomain";
+in
+{
+  name = "sourcehut";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ tomberek nessdoor ];
+
+  nodes.machine = { config, pkgs, nodes, ... }: {
+    imports = [
+      ./nodes/common.nix
+    ];
+
+    networking.domain = domain;
+    networking.extraHosts = ''
+      ${config.networking.primaryIPAddress} git.${domain}
+      ${config.networking.primaryIPAddress} meta.${domain}
+    '';
+
+    services.sourcehut = {
+      git.enable = true;
+      settings."git.sr.ht" = {
+        oauth-client-secret = pkgs.writeText "gitsrht-oauth-client-secret" "3597288dc2c716e567db5384f493b09d";
+        oauth-client-id = "d07cb713d920702e";
+      };
+    };
+
+    environment.systemPackages = with pkgs; [
+      git
+    ];
+  };
+
+  testScript =
+    let
+      userName = "nixos-test";
+      userPass = "AutoNixosTestPwd";
+      hutConfig = pkgs.writeText "hut-config" ''
+        instance "${domain}" {
+          # Will be replaced at runtime with the generated token
+          access-token "OAUTH-TOKEN"
+        }
+      '';
+      sshConfig = pkgs.writeText "ssh-config" ''
+        Host git.${domain}
+             IdentityFile = ~/.ssh/id_rsa
+      '';
+    in
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("sshd.service")
+
+      with subtest("Check whether meta comes up"):
+           machine.wait_for_unit("metasrht-api.service")
+           machine.wait_for_unit("metasrht.service")
+           machine.wait_for_unit("metasrht-webhooks.service")
+           machine.wait_for_open_port(5000)
+           machine.succeed("curl -sL http://localhost:5000 | grep meta.${domain}")
+           machine.succeed("curl -sL http://meta.${domain} | grep meta.${domain}")
+
+      with subtest("Create a new user account and OAuth access key"):
+           machine.succeed("echo ${userPass} | metasrht-manageuser -ps -e ${userName}@${domain}\
+                            -t active_paying ${userName}");
+           (_, token) = machine.execute("srht-gen-oauth-tok -i ${domain} -q ${userName} ${userPass}")
+           token = token.strip().replace("/", r"\\/") # Escape slashes in token before passing it to sed
+           machine.execute("mkdir -p ~/.config/hut/")
+           machine.execute("sed s/OAUTH-TOKEN/" + token + "/ ${hutConfig} > ~/.config/hut/config")
+
+      with subtest("Check whether git comes up"):
+           machine.wait_for_unit("gitsrht-api.service")
+           machine.wait_for_unit("gitsrht.service")
+           machine.wait_for_unit("gitsrht-webhooks.service")
+           machine.succeed("curl -sL http://git.${domain} | grep git.${domain}")
+
+      with subtest("Add an SSH key for Git access"):
+           machine.execute("ssh-keygen -q -N \"\" -t rsa -f ~/.ssh/id_rsa")
+           machine.execute("cat ${sshConfig} > ~/.ssh/config")
+           machine.succeed("hut meta ssh-key create ~/.ssh/id_rsa.pub")
+
+      with subtest("Create a new repo and push contents to it"):
+           machine.execute("git init test")
+           machine.execute("echo \"Hello world!\" > test/hello.txt")
+           machine.execute("cd test && git add .")
+           machine.execute("cd test && git commit -m \"Initial commit\"")
+           machine.execute("cd test && git tag v0.1")
+           machine.succeed("cd test && git remote add origin gitsrht@git.${domain}:~${userName}/test")
+           machine.execute("( echo -n 'git.${domain} '; cat /etc/ssh/ssh_host_ed25519_key.pub ) > ~/.ssh/known_hosts")
+           machine.succeed("hut git create test")
+           machine.succeed("cd test && git push --tags --set-upstream origin master")
+
+      with subtest("Verify that the repo is downloadable and its contents match the original"):
+           machine.succeed("curl https://git.${domain}/~${userName}/test/archive/v0.1.tar.gz | tar -xz")
+           machine.succeed("diff test-v0.1/hello.txt test/hello.txt")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/sourcehut/nodes/common.nix b/nixpkgs/nixos/tests/sourcehut/nodes/common.nix
new file mode 100644
index 000000000000..f0a81358f972
--- /dev/null
+++ b/nixpkgs/nixos/tests/sourcehut/nodes/common.nix
@@ -0,0 +1,107 @@
+{ config, pkgs, nodes, ... }:
+let
+  domain = config.networking.domain;
+
+  # Note that wildcard certificates just under the TLD (eg. *.com)
+  # would be rejected by clients like curl.
+  tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
+      -subj '/CN=${domain}' -extensions v3_req \
+      -addext 'subjectAltName = DNS:*.${domain}'
+    install -D -t $out key.pem cert.pem
+  '';
+in
+{
+  # buildsrht needs space
+  virtualisation.diskSize = 4 * 1024;
+  virtualisation.memorySize = 2 * 1024;
+  networking.enableIPv6 = false;
+
+  services.sourcehut = {
+    enable = true;
+    nginx.enable = true;
+    nginx.virtualHost = {
+      forceSSL = true;
+      sslCertificate = "${tls-cert}/cert.pem";
+      sslCertificateKey = "${tls-cert}/key.pem";
+    };
+    postgresql.enable = true;
+    redis.enable = true;
+
+    meta.enable = true;
+
+    settings."sr.ht" = {
+      environment = "production";
+      global-domain = config.networking.domain;
+      service-key = pkgs.writeText "service-key" "8b327279b77e32a3620e2fc9aabce491cc46e7d821fd6713b2a2e650ce114d01";
+      network-key = pkgs.writeText "network-key" "cEEmc30BRBGkgQZcHFksiG7hjc6_dK1XR2Oo5Jb9_nQ=";
+    };
+    settings.webhooks.private-key = pkgs.writeText "webhook-key" "Ra3IjxgFiwG9jxgp4WALQIZw/BMYt30xWiOsqD0J7EA=";
+    settings.mail = {
+      smtp-from = "root+hut@${domain}";
+      # WARNING: take care to keep pgp-privkey outside the Nix store in production,
+      # or use LoadCredentialEncrypted=
+      pgp-privkey = toString (pkgs.writeText "sourcehut.pgp-privkey" ''
+        -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+        lFgEYqDRORYJKwYBBAHaRw8BAQdAehGoy36FUx2OesYm07be2rtLyvR5Pb/ltstd
+        Gk7hYQoAAP9X4oPmxxrHN8LewBpWITdBomNqlHoiP7mI0nz/BOPJHxEktDZuaXhv
+        cy90ZXN0cy9zb3VyY2VodXQgPHJvb3QraHV0QHNvdXJjZWh1dC5sb2NhbGRvbWFp
+        bj6IlwQTFgoAPxYhBPqjgjnL8RHN4JnADNicgXaYm0jJBQJioNE5AhsDBQkDwmcA
+        BgsJCAcDCgUVCgkICwUWAwIBAAIeBQIXgAAKCRDYnIF2mJtIySVCAP9e2nHsVHSi
+        2B1YGZpVG7Xf36vxljmMkbroQy+0gBPwRwEAq+jaiQqlbGhQ7R/HMFcAxBIVsq8h
+        Aw1rngsUd0o3dAicXQRioNE5EgorBgEEAZdVAQUBAQdAXZV2Sd5ZNBVTBbTGavMv
+        D6ORrUh8z7TI/3CsxCE7+yADAQgHAAD/c1RU9xH+V/uI1fE7HIn/zL0LUPpsuce2
+        cH++g4u3kBgTOYh+BBgWCgAmFiEE+qOCOcvxEc3gmcAM2JyBdpibSMkFAmKg0TkC
+        GwwFCQPCZwAACgkQ2JyBdpibSMlKagD/cTre6p1m8QuJ7kwmCFRSz5tBzIuYMMgN
+        xtT7dmS91csA/35fWsOykSiFRojQ7ccCSUTHL7ApF2EbL968tP/D2hIG
+        =Hjoc
+        -----END PGP PRIVATE KEY BLOCK-----
+      '');
+      pgp-pubkey = pkgs.writeText "sourcehut.pgp-pubkey" ''
+        -----BEGIN PGP PUBLIC KEY BLOCK-----
+
+        mDMEYqDRORYJKwYBBAHaRw8BAQdAehGoy36FUx2OesYm07be2rtLyvR5Pb/ltstd
+        Gk7hYQq0Nm5peG9zL3Rlc3RzL3NvdXJjZWh1dCA8cm9vdCtodXRAc291cmNlaHV0
+        LmxvY2FsZG9tYWluPoiXBBMWCgA/FiEE+qOCOcvxEc3gmcAM2JyBdpibSMkFAmKg
+        0TkCGwMFCQPCZwAGCwkIBwMKBRUKCQgLBRYDAgEAAh4FAheAAAoJENicgXaYm0jJ
+        JUIA/17acexUdKLYHVgZmlUbtd/fq/GWOYyRuuhDL7SAE/BHAQCr6NqJCqVsaFDt
+        H8cwVwDEEhWyryEDDWueCxR3Sjd0CLg4BGKg0TkSCisGAQQBl1UBBQEBB0BdlXZJ
+        3lk0FVMFtMZq8y8Po5GtSHzPtMj/cKzEITv7IAMBCAeIfgQYFgoAJhYhBPqjgjnL
+        8RHN4JnADNicgXaYm0jJBQJioNE5AhsMBQkDwmcAAAoJENicgXaYm0jJSmoA/3E6
+        3uqdZvELie5MJghUUs+bQcyLmDDIDcbU+3ZkvdXLAP9+X1rDspEohUaI0O3HAklE
+        xy+wKRdhGy/evLT/w9oSBg==
+        =pJD7
+        -----END PGP PUBLIC KEY BLOCK-----
+      '';
+      pgp-key-id = "0xFAA38239CBF111CDE099C00CD89C8176989B48C9";
+    };
+  };
+
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
+  security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+  services.nginx = {
+    enable = true;
+    recommendedGzipSettings = true;
+    recommendedOptimisation = true;
+    recommendedTlsSettings = true;
+    recommendedProxySettings = true;
+  };
+
+  services.postgresql = {
+    enable = true;
+    enableTCPIP = false;
+    settings.unix_socket_permissions = "0770";
+  };
+
+  services.openssh = {
+    enable = true;
+    settings.PasswordAuthentication = false;
+    settings.PermitRootLogin = "no";
+  };
+
+  environment.systemPackages = with pkgs; [
+    hut # For interacting with the Sourcehut APIs via CLI
+    srht-gen-oauth-tok # To automatically generate user OAuth tokens
+  ];
+}
diff --git a/nixpkgs/nixos/tests/spacecookie.nix b/nixpkgs/nixos/tests/spacecookie.nix
new file mode 100644
index 000000000000..a640657d8a6b
--- /dev/null
+++ b/nixpkgs/nixos/tests/spacecookie.nix
@@ -0,0 +1,56 @@
+let
+  gopherRoot   = "/tmp/gopher";
+  gopherHost   = "gopherd";
+  gopherClient = "client";
+  fileContent  = "Hello Gopher!\n";
+  fileName     = "file.txt";
+in
+  import ./make-test-python.nix ({...}: {
+    name = "spacecookie";
+    nodes = {
+      ${gopherHost} = {
+        systemd.services.spacecookie = {
+          preStart = ''
+            mkdir -p ${gopherRoot}/directory
+            printf "%s" "${fileContent}" > ${gopherRoot}/${fileName}
+          '';
+        };
+
+        services.spacecookie = {
+          enable = true;
+          openFirewall = true;
+          settings = {
+            root = gopherRoot;
+            hostname = gopherHost;
+          };
+        };
+      };
+
+      ${gopherClient} = {};
+    };
+
+    testScript = ''
+      start_all()
+
+      # with daemon type notify, the unit being started
+      # should also mean the port is open
+      ${gopherHost}.wait_for_unit("spacecookie.service")
+      ${gopherClient}.wait_for_unit("network.target")
+
+      fileResponse = ${gopherClient}.succeed("curl -f -s gopher://${gopherHost}/0/${fileName}")
+
+      # the file response should return our created file exactly
+      if not (fileResponse == "${builtins.replaceStrings [ "\n" ] [ "\\n" ] fileContent}"):
+          raise Exception("Unexpected file response")
+
+      # sanity check on the directory listing: we serve a directory and a file
+      # via gopher, so the directory listing should have exactly two entries,
+      # one with gopher file type 0 (file) and one with file type 1 (directory).
+      dirResponse = ${gopherClient}.succeed("curl -f -s gopher://${gopherHost}")
+      dirEntries = [l[0] for l in dirResponse.split("\n") if len(l) > 0]
+      dirEntries.sort()
+
+      if not (["0", "1"] == dirEntries):
+          raise Exception("Unexpected directory response")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/spark/default.nix b/nixpkgs/nixos/tests/spark/default.nix
new file mode 100644
index 000000000000..034e9711bed5
--- /dev/null
+++ b/nixpkgs/nixos/tests/spark/default.nix
@@ -0,0 +1,48 @@
+{ pkgs, ... }:
+
+let
+  inherit (pkgs) lib;
+  tests = {
+    default = testsForPackage { sparkPackage = pkgs.spark; };
+  };
+
+  testsForPackage = args: lib.recurseIntoAttrs {
+    sparkCluster = testSparkCluster args;
+    passthru.override = args': testsForPackage (args // args');
+  };
+  testSparkCluster = { sparkPackage, ... }: pkgs.testers.nixosTest ({
+    name = "spark";
+
+    nodes = {
+      worker = { nodes, pkgs, ... }: {
+        services.spark = {
+          package = sparkPackage;
+          worker = {
+            enable = true;
+            master = "master:7077";
+          };
+        };
+        virtualisation.memorySize = 2048;
+      };
+      master = { config, pkgs, ... }: {
+        services.spark = {
+          package = sparkPackage;
+          master = {
+            enable = true;
+            bind = "0.0.0.0";
+          };
+        };
+        networking.firewall.allowedTCPPorts = [ 22 7077 8080 ];
+      };
+    };
+
+    testScript = ''
+      master.wait_for_unit("spark-master.service")
+      worker.wait_for_unit("spark-worker.service")
+      worker.copy_from_host( "${./spark_sample.py}", "/spark_sample.py" )
+      assert "<title>Spark Master at spark://" in worker.succeed("curl -sSfkL http://master:8080/")
+      worker.succeed("spark-submit --version | systemd-cat")
+      worker.succeed("spark-submit --master spark://master:7077 --executor-memory 512m --executor-cores 1 /spark_sample.py")
+    '';
+  });
+in tests
diff --git a/nixpkgs/nixos/tests/spark/spark_sample.py b/nixpkgs/nixos/tests/spark/spark_sample.py
new file mode 100644
index 000000000000..c4939451eae0
--- /dev/null
+++ b/nixpkgs/nixos/tests/spark/spark_sample.py
@@ -0,0 +1,40 @@
+from pyspark.sql import Row, SparkSession
+from pyspark.sql import functions as F
+from pyspark.sql.functions import udf
+from pyspark.sql.types import *
+from pyspark.sql.functions import explode
+
+def explode_col(weight):
+    return int(weight//10) * [10.0] + ([] if weight%10==0 else [weight%10])
+
+spark = SparkSession.builder.getOrCreate()
+
+dataSchema = [
+    StructField("feature_1", FloatType()),
+    StructField("feature_2", FloatType()),
+    StructField("bias_weight", FloatType())
+]
+
+data = [
+    Row(0.1, 0.2, 10.32),
+    Row(0.32, 1.43, 12.8),
+    Row(1.28, 1.12, 0.23)
+]
+
+df = spark.createDataFrame(spark.sparkContext.parallelize(data), StructType(dataSchema))
+
+normalizing_constant = 100
+sum_bias_weight = df.select(F.sum('bias_weight')).collect()[0][0]
+normalizing_factor = normalizing_constant / sum_bias_weight
+df = df.withColumn('normalized_bias_weight', df.bias_weight * normalizing_factor)
+df = df.drop('bias_weight')
+df = df.withColumnRenamed('normalized_bias_weight', 'bias_weight')
+
+my_udf = udf(lambda x: explode_col(x), ArrayType(FloatType()))
+df1 = df.withColumn('explode_val', my_udf(df.bias_weight))
+df1 = df1.withColumn("explode_val_1", explode(df1.explode_val)).drop("explode_val")
+df1 = df1.drop('bias_weight').withColumnRenamed('explode_val_1', 'bias_weight')
+
+df1.show()
+
+assert(df1.count() == 12)
diff --git a/nixpkgs/nixos/tests/sqlite3-to-mysql.nix b/nixpkgs/nixos/tests/sqlite3-to-mysql.nix
new file mode 100644
index 000000000000..f18a442157e7
--- /dev/null
+++ b/nixpkgs/nixos/tests/sqlite3-to-mysql.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+/*
+  This test suite replaces the typical pytestCheckHook function in
+  sqlite3-to-mysql due to the need of a running mysql instance.
+*/
+
+{
+  name = "sqlite3-to-mysql";
+  meta.maintainers = with lib.maintainers; [ gador ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [
+      sqlite3-to-mysql
+      # create one coherent python environment
+      (python3.withPackages
+        (ps: sqlite3-to-mysql.propagatedBuildInputs ++
+          [
+            python3Packages.pytest
+            python3Packages.pytest-mock
+            python3Packages.pytest-timeout
+            python3Packages.factory-boy
+            python3Packages.docker # only needed so import does not fail
+            sqlite3-to-mysql
+          ])
+      )
+    ];
+    services.mysql = {
+      package = pkgs.mariadb;
+      enable = true;
+      # from https://github.com/techouse/sqlite3-to-mysql/blob/master/tests/conftest.py
+      # and https://github.com/techouse/sqlite3-to-mysql/blob/master/.github/workflows/test.yml
+      initialScript = pkgs.writeText "mysql-init.sql" ''
+        create database test_db DEFAULT CHARACTER SET utf8mb4;
+        create user tester identified by 'testpass';
+        grant all on test_db.* to tester;
+        create user tester@localhost identified by 'testpass';
+        grant all on test_db.* to tester@localhost;
+      '';
+      settings = {
+        mysqld = {
+          character-set-server = "utf8mb4";
+          collation-server = "utf8mb4_unicode_ci";
+          log_warnings = 1;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("mysql")
+
+    machine.succeed(
+         "sqlite3mysql --version | grep ${pkgs.sqlite3-to-mysql.version}"
+    )
+
+    # invalid_database_name: assert '1045 (28000): Access denied' in "1044 (42000): Access denied [...]
+    # invalid_database_user: does not return non-zero exit for some reason
+    # test_version: has problems importing sqlite3_to_mysql and determining the version
+    machine.succeed(
+         "cd ${pkgs.sqlite3-to-mysql.src} \
+          && pytest -v --no-docker -k \"not test_invalid_database_name and not test_invalid_database_user and not test_version\""
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ssh-agent-auth.nix b/nixpkgs/nixos/tests/ssh-agent-auth.nix
new file mode 100644
index 000000000000..fee40afd6153
--- /dev/null
+++ b/nixpkgs/nixos/tests/ssh-agent-auth.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+  let
+    inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+  in {
+    name = "ssh-agent-auth";
+    meta.maintainers = with lib.maintainers; [ nicoo ];
+
+    nodes = let nodeConfig = n: { ... }: {
+      users.users = {
+        admin = {
+          isNormalUser = true;
+          extraGroups = [ "wheel" ];
+          openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+        };
+        foo.isNormalUser = true;
+      };
+
+      security.pam.sshAgentAuth = {
+        # Must be specified, as nixpkgs CI expects everything to eval without warning
+        authorizedKeysFiles = [ "/etc/ssh/authorized_keys.d/%u" ];
+        enable = true;
+      };
+      security.${lib.replaceStrings [ "_" ] [ "-" ] n} = {
+        enable = true;
+        wheelNeedsPassword = true;  # We are checking `pam_ssh_agent_auth(8)` works for a sudoer
+      };
+
+      # Necessary for pam_ssh_agent_auth  >_>'
+      services.openssh.enable = true;
+    };
+    in lib.genAttrs [ "sudo" "sudo_rs" ] nodeConfig;
+
+    testScript = let
+      privateKeyPath = "/home/admin/.ssh/id_ecdsa";
+      userScript = pkgs.writeShellScript "test-script" ''
+        set -e
+        ssh-add -q ${privateKeyPath}
+
+        # faketty needed to ensure `sudo` doesn't write to the controlling PTY,
+        #  which would break the test-driver's line-oriented protocol.
+        ${lib.getExe pkgs.faketty} sudo -u foo -- id -un
+      '';
+    in ''
+      for vm in (sudo, sudo_rs):
+        sudo_impl = vm.name.replace("_", "-")
+        with subtest(f"wheel user can auth with ssh-agent for {sudo_impl}"):
+            vm.copy_from_host("${snakeOilPrivateKey}", "${privateKeyPath}")
+            vm.succeed("chmod -R 0700 /home/admin")
+            vm.succeed("chown -R admin:users /home/admin")
+
+            # Run `userScript` in an environment with an SSH-agent available
+            assert vm.succeed("sudo -u admin -- ssh-agent ${userScript} 2>&1").strip() == "foo"
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/ssh-audit.nix b/nixpkgs/nixos/tests/ssh-audit.nix
new file mode 100644
index 000000000000..25772aba3ea0
--- /dev/null
+++ b/nixpkgs/nixos/tests/ssh-audit.nix
@@ -0,0 +1,104 @@
+import ./make-test-python.nix (
+  {pkgs, ...}: let
+    sshKeys = import (pkgs.path + "/nixos/tests/ssh-keys.nix") pkgs;
+    sshUsername = "any-user";
+    serverName = "server";
+    clientName = "client";
+    sshAuditPort = 2222;
+  in {
+    name = "ssh";
+
+    nodes = {
+      "${serverName}" = {
+        networking.firewall.allowedTCPPorts = [
+          sshAuditPort
+        ];
+        services.openssh.enable = true;
+        users.users."${sshUsername}" = {
+          isNormalUser = true;
+          openssh.authorizedKeys.keys = [
+            sshKeys.snakeOilPublicKey
+          ];
+        };
+      };
+      "${clientName}" = {
+        programs.ssh = {
+          ciphers = [
+            "aes128-ctr"
+            "aes128-gcm@openssh.com"
+            "aes192-ctr"
+            "aes256-ctr"
+            "aes256-gcm@openssh.com"
+            "chacha20-poly1305@openssh.com"
+          ];
+          extraConfig = ''
+            IdentitiesOnly yes
+          '';
+          hostKeyAlgorithms = [
+            "rsa-sha2-256"
+            "rsa-sha2-256-cert-v01@openssh.com"
+            "rsa-sha2-512"
+            "rsa-sha2-512-cert-v01@openssh.com"
+            "sk-ssh-ed25519-cert-v01@openssh.com"
+            "sk-ssh-ed25519@openssh.com"
+            "ssh-ed25519"
+            "ssh-ed25519-cert-v01@openssh.com"
+          ];
+          kexAlgorithms = [
+            "curve25519-sha256"
+            "curve25519-sha256@libssh.org"
+            "diffie-hellman-group-exchange-sha256"
+            "diffie-hellman-group16-sha512"
+            "diffie-hellman-group18-sha512"
+            "sntrup761x25519-sha512@openssh.com"
+          ];
+          macs = [
+            "hmac-sha2-256-etm@openssh.com"
+            "hmac-sha2-512-etm@openssh.com"
+            "umac-128-etm@openssh.com"
+          ];
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${serverName}.wait_for_open_port(22)
+
+      # Should pass SSH server audit
+      ${serverName}.succeed("${pkgs.ssh-audit}/bin/ssh-audit 127.0.0.1")
+
+      # Wait for client to be able to connect to the server
+      ${clientName}.systemctl("start network-online.target")
+      ${clientName}.wait_for_unit("network-online.target")
+
+      # Set up trusted private key
+      ${clientName}.succeed("cat ${sshKeys.snakeOilPrivateKey} > privkey.snakeoil")
+      ${clientName}.succeed("chmod 600 privkey.snakeoil")
+
+      # Fail fast and disable interactivity
+      ssh_options = "-o BatchMode=yes -o ConnectTimeout=1 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
+
+      # Should deny root user
+      ${clientName}.fail(f"ssh {ssh_options} root@${serverName} true")
+
+      # Should deny non-root user password login
+      ${clientName}.fail(f"ssh {ssh_options} -o PasswordAuthentication=yes ${sshUsername}@${serverName} true")
+
+      # Should allow non-root user certificate login
+      ${clientName}.succeed(f"ssh {ssh_options} -i privkey.snakeoil ${sshUsername}@${serverName} true")
+
+      # Should pass SSH client audit
+      service_name = "ssh-audit.service"
+      ${serverName}.succeed(f"systemd-run --unit={service_name} ${pkgs.ssh-audit}/bin/ssh-audit --client-audit --port=${toString sshAuditPort}")
+      ${clientName}.sleep(5) # We can't use wait_for_open_port because ssh-audit exits as soon as anything talks to it
+      ${clientName}.execute(
+          f"ssh {ssh_options} -i privkey.snakeoil -p ${toString sshAuditPort} ${sshUsername}@${serverName} true",
+          check_return=False,
+          timeout=10
+      )
+      ${serverName}.succeed(f"exit $(systemctl show --property=ExecMainStatus --value {service_name})")
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/ssh-keys.nix b/nixpkgs/nixos/tests/ssh-keys.nix
new file mode 100644
index 000000000000..df9ff38a3b22
--- /dev/null
+++ b/nixpkgs/nixos/tests/ssh-keys.nix
@@ -0,0 +1,15 @@
+pkgs:
+{ snakeOilPrivateKey = pkgs.writeText "privkey.snakeoil" ''
+    -----BEGIN EC PRIVATE KEY-----
+    MHcCAQEEIHQf/khLvYrQ8IOika5yqtWvI0oquHlpRLTZiJy5dRJmoAoGCCqGSM49
+    AwEHoUQDQgAEKF0DYGbBwbj06tA3fd/+yP44cvmwmHBWXZCKbS+RQlAKvLXMWkpN
+    r1lwMyJZoSGgBHoUahoYjTh9/sJL7XLJtA==
+    -----END EC PRIVATE KEY-----
+  '';
+
+  snakeOilPublicKey = pkgs.lib.concatStrings [
+    "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHA"
+    "yNTYAAABBBChdA2BmwcG49OrQN33f/sj+OHL5sJhwVl2Qim0vkUJQCry1zFpKTa"
+    "9ZcDMiWaEhoAR6FGoaGI04ff7CS+1yybQ= snakeoil"
+  ];
+}
diff --git a/nixpkgs/nixos/tests/sslh.nix b/nixpkgs/nixos/tests/sslh.nix
new file mode 100644
index 000000000000..30ffd389d442
--- /dev/null
+++ b/nixpkgs/nixos/tests/sslh.nix
@@ -0,0 +1,75 @@
+import ./make-test-python.nix {
+  name = "sslh";
+
+  nodes = {
+    server = { pkgs, lib, ... }: {
+      networking.firewall.allowedTCPPorts = [ 443 ];
+      networking.interfaces.eth1.ipv6.addresses = [
+        {
+          address = "fe00:aa:bb:cc::2";
+          prefixLength = 64;
+        }
+      ];
+      services.sslh = {
+        enable = true;
+        settings.transparent = true;
+        settings.protocols = [
+          { name = "ssh"; service = "ssh"; host = "localhost"; port = "22"; probe = "builtin"; }
+          { name = "http"; host = "localhost"; port = "80"; probe = "builtin"; }
+        ];
+      };
+      services.openssh.enable = true;
+      users.users.root.openssh.authorizedKeys.keyFiles = [ ./initrd-network-ssh/id_ed25519.pub ];
+      services.nginx = {
+        enable = true;
+        virtualHosts."localhost" = {
+          addSSL = false;
+          default = true;
+          root = pkgs.runCommand "testdir" {} ''
+            mkdir "$out"
+            echo hello world > "$out/index.html"
+          '';
+        };
+      };
+    };
+    client = { ... }: {
+      networking.interfaces.eth1.ipv6.addresses = [
+        {
+          address = "fe00:aa:bb:cc::1";
+          prefixLength = 64;
+        }
+      ];
+      networking.hosts."fe00:aa:bb:cc::2" = [ "server" ];
+      environment.etc.sshKey = {
+        source = ./initrd-network-ssh/id_ed25519; # dont use this anywhere else
+        mode = "0600";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("sslh.service")
+    server.wait_for_unit("nginx.service")
+    server.wait_for_unit("sshd.service")
+    server.wait_for_open_port(80)
+    server.wait_for_open_port(443)
+    server.wait_for_open_port(22)
+
+    for arg in ["-6", "-4"]:
+        client.wait_until_succeeds(f"ping {arg} -c1 server")
+
+        # check that ssh through sslh works
+        client.succeed(
+            f"ssh {arg} -p 443 -i /etc/sshKey -o StrictHostKeyChecking=accept-new server 'echo $SSH_CONNECTION > /tmp/foo{arg}'"
+        )
+
+        # check that 1/ the above ssh command had an effect 2/ transparent proxying really works
+        ip = "fe00:aa:bb:cc::1" if arg == "-6" else "192.168.1."
+        server.succeed(f"grep '{ip}' /tmp/foo{arg}")
+
+        # check that http through sslh works
+        assert client.succeed(f"curl -f {arg} http://server:443").strip() == "hello world"
+  '';
+}
diff --git a/nixpkgs/nixos/tests/sssd-ldap.nix b/nixpkgs/nixos/tests/sssd-ldap.nix
new file mode 100644
index 000000000000..60f3b1a415da
--- /dev/null
+++ b/nixpkgs/nixos/tests/sssd-ldap.nix
@@ -0,0 +1,173 @@
+let
+  dbDomain = "example.org";
+  dbSuffix = "dc=example,dc=org";
+
+  ldapRootUser = "admin";
+  ldapRootPassword = "foobar";
+
+  testUser = "alice";
+  testPassword = "foobar";
+  testNewPassword = "barfoo";
+in
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "sssd-ldap";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ bbigras s1341 ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    security.pam.services.systemd-user.makeHomeDir = true;
+    environment.etc."cert.pem".text = builtins.readFile ./common/acme/server/acme.test.cert.pem;
+    environment.etc."key.pem".text = builtins.readFile ./common/acme/server/acme.test.key.pem;
+    services.openldap = {
+      enable = true;
+      urlList = [ "ldap:///" "ldaps:///" ];
+      settings = {
+        attrs = {
+          olcTLSCACertificateFile = "/etc/cert.pem";
+          olcTLSCertificateFile = "/etc/cert.pem";
+          olcTLSCertificateKeyFile = "/etc/key.pem";
+          olcTLSCipherSuite = "HIGH:MEDIUM:+3DES:+RC4:+aNULL";
+          olcTLSCRLCheck = "none";
+          olcTLSVerifyClient = "never";
+          olcTLSProtocolMin = "3.1";
+        };
+        children = {
+          "cn=schema".includes = [
+            "${pkgs.openldap}/etc/schema/core.ldif"
+            "${pkgs.openldap}/etc/schema/cosine.ldif"
+            "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+            "${pkgs.openldap}/etc/schema/nis.ldif"
+          ];
+          "olcDatabase={1}mdb" = {
+            attrs = {
+              objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+              olcDatabase = "{1}mdb";
+              olcDbDirectory = "/var/lib/openldap/db";
+              olcSuffix = dbSuffix;
+              olcRootDN = "cn=${ldapRootUser},${dbSuffix}";
+              olcRootPW = ldapRootPassword;
+              olcAccess = [
+                /*
+                  custom access rules for userPassword attributes
+                  */
+                ''
+                  {0}to attrs=userPassword
+                                    by self write
+                                    by anonymous auth
+                                    by * none''
+
+                /*
+                  allow read on anything else
+                  */
+                ''
+                  {1}to *
+                                    by * read''
+              ];
+            };
+          };
+        };
+      };
+      declarativeContents = {
+        ${dbSuffix} = ''
+          dn: ${dbSuffix}
+          objectClass: top
+          objectClass: dcObject
+          objectClass: organization
+          o: ${dbDomain}
+
+          dn: ou=posix,${dbSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: ou=accounts,ou=posix,${dbSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: uid=${testUser},ou=accounts,ou=posix,${dbSuffix}
+          objectClass: person
+          objectClass: posixAccount
+          userPassword: ${testPassword}
+          homeDirectory: /home/${testUser}
+          uidNumber: 1234
+          gidNumber: 1234
+          cn: ""
+          sn: ""
+        '';
+      };
+    };
+
+    services.sssd = {
+      enable = true;
+      # just for testing purposes, don't put this into the Nix store in production!
+      environmentFile = "${pkgs.writeText "ldap-root" "LDAP_BIND_PW=${ldapRootPassword}"}";
+      config = ''
+        [sssd]
+        config_file_version = 2
+        services = nss, pam, sudo
+        domains = ${dbDomain}
+
+        [domain/${dbDomain}]
+        auth_provider = ldap
+        id_provider = ldap
+        ldap_uri = ldaps://127.0.0.1:636
+        ldap_tls_reqcert = allow
+        ldap_tls_cacert = /etc/cert.pem
+        ldap_search_base = ${dbSuffix}
+        ldap_default_bind_dn = cn=${ldapRootUser},${dbSuffix}
+        ldap_default_authtok_type = password
+        ldap_default_authtok = $LDAP_BIND_PW
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("openldap.service")
+    machine.wait_for_unit("sssd.service")
+    result = machine.execute("getent passwd ${testUser}")
+    if result[0] == 0:
+      assert "${testUser}" in result[1]
+    else:
+      machine.wait_for_console_text("Backend is online")
+      machine.succeed("getent passwd ${testUser}")
+
+    with subtest("Log in as ${testUser}"):
+        machine.wait_until_tty_matches("1", "login: ")
+        machine.send_chars("${testUser}\n")
+        machine.wait_until_tty_matches("1", "login: ${testUser}")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches("1", "Password: ")
+        machine.send_chars("${testPassword}\n")
+        machine.wait_until_succeeds("pgrep -u ${testUser} bash")
+        machine.send_chars("touch done\n")
+        machine.wait_for_file("/home/${testUser}/done")
+
+    with subtest("Change ${testUser}'s password"):
+        machine.send_chars("passwd\n")
+        machine.wait_until_tty_matches("1", "Current Password: ")
+        machine.send_chars("${testPassword}\n")
+        machine.wait_until_tty_matches("1", "New Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_tty_matches("1", "Reenter new Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_tty_matches("1", "passwd: password updated successfully")
+
+    with subtest("Log in as ${testUser} with new password in virtual console 2"):
+        machine.send_key("alt-f2")
+        machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+        machine.wait_for_unit("getty@tty2.service")
+        machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+
+        machine.wait_until_tty_matches("2", "login: ")
+        machine.send_chars("${testUser}\n")
+        machine.wait_until_tty_matches("2", "login: ${testUser}")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches("2", "Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_succeeds("pgrep -u ${testUser} bash")
+        machine.send_chars("touch done2\n")
+        machine.wait_for_file("/home/${testUser}/done2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sssd.nix b/nixpkgs/nixos/tests/sssd.nix
new file mode 100644
index 000000000000..c8d356e074ad
--- /dev/null
+++ b/nixpkgs/nixos/tests/sssd.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "sssd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ bbigras ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    services.sssd.enable = true;
+  };
+
+  testScript = ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("sssd.service")
+      machine.succeed("sssctl config-check")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/stalwart-mail.nix b/nixpkgs/nixos/tests/stalwart-mail.nix
new file mode 100644
index 000000000000..634c0e2e3926
--- /dev/null
+++ b/nixpkgs/nixos/tests/stalwart-mail.nix
@@ -0,0 +1,120 @@
+# Rudimentary test checking that the Stalwart email server can:
+# - receive some message through SMTP submission, then
+# - serve this message through IMAP.
+
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+
+in import ./make-test-python.nix ({ lib, ... }: {
+  name = "stalwart-mail";
+
+  nodes.main = { pkgs, ... }: {
+    security.pki.certificateFiles = [ certs.ca.cert ];
+
+    services.stalwart-mail = {
+      enable = true;
+      settings = {
+        server.hostname = domain;
+
+        certificate."snakeoil" = {
+          cert = "file://${certs.${domain}.cert}";
+          private-key = "file://${certs.${domain}.key}";
+        };
+
+        server.tls = {
+          certificate = "snakeoil";
+          enable = true;
+          implicit = false;
+        };
+
+        server.listener = {
+          "smtp-submission" = {
+            bind = [ "[::]:587" ];
+            protocol = "smtp";
+          };
+
+          "imap" = {
+            bind = [ "[::]:143" ];
+            protocol = "imap";
+          };
+        };
+
+        session.auth.mechanisms = [ "PLAIN" ];
+        session.auth.directory = "in-memory";
+        storage.directory = "in-memory";  # shared with imap
+
+        session.rcpt.directory = "in-memory";
+        queue.outbound.next-hop = [ "local" ];
+
+        directory."in-memory" = {
+          type = "memory";
+          principals = [
+            {
+              type = "individual";
+              name = "alice";
+              secret = "foobar";
+              email = [ "alice@${domain}" ];
+            }
+            {
+              type = "individual";
+              name = "bob";
+              secret = "foobar";
+              email = [ "bob@${domain}" ];
+            }
+          ];
+        };
+      };
+    };
+
+    environment.systemPackages = [
+      (pkgs.writers.writePython3Bin "test-smtp-submission" { } ''
+        from smtplib import SMTP
+
+        with SMTP('localhost', 587) as smtp:
+            smtp.starttls()
+            smtp.login('alice', 'foobar')
+            smtp.sendmail(
+                'alice@${domain}',
+                'bob@${domain}',
+                """
+                    From: alice@${domain}
+                    To: bob@${domain}
+                    Subject: Some test message
+
+                    This is a test message.
+                """.strip()
+            )
+      '')
+
+      (pkgs.writers.writePython3Bin "test-imap-read" { } ''
+        from imaplib import IMAP4
+
+        with IMAP4('localhost') as imap:
+            imap.starttls()
+            status, [caps] = imap.login('bob', 'foobar')
+            assert status == 'OK'
+            imap.select()
+            status, [ref] = imap.search(None, 'ALL')
+            assert status == 'OK'
+            [msgId] = ref.split()
+            status, msg = imap.fetch(msgId, 'BODY[TEXT]')
+            assert status == 'OK'
+            assert msg[0][1].strip() == b'This is a test message.'
+      '')
+    ];
+  };
+
+  testScript = /* python */ ''
+    main.wait_for_unit("stalwart-mail.service")
+    main.wait_for_open_port(587)
+    main.wait_for_open_port(143)
+
+    main.succeed("test-smtp-submission")
+    main.succeed("test-imap-read")
+  '';
+
+  meta = {
+    maintainers = with lib.maintainers; [ happysalada pacien ];
+  };
+})
diff --git a/nixpkgs/nixos/tests/starship.nix b/nixpkgs/nixos/tests/starship.nix
new file mode 100644
index 000000000000..48a4be6caf17
--- /dev/null
+++ b/nixpkgs/nixos/tests/starship.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "starship";
+  meta.maintainers = pkgs.starship.meta.maintainers;
+
+  nodes.machine = {
+    programs = {
+      fish.enable = true;
+      zsh.enable = true;
+
+      starship = {
+        enable = true;
+        settings.format = "<starship>";
+      };
+    };
+
+    environment.systemPackages = map
+      (shell: pkgs.writeScriptBin "expect-${shell}" ''
+        #!${pkgs.expect}/bin/expect -f
+
+        spawn env TERM=xterm ${shell} -i
+
+        expect "<starship>" {
+          send "exit\n"
+        } timeout {
+          send_user "\n${shell} failed to display Starship\n"
+          exit 1
+        }
+
+        expect eof
+      '')
+      [ "bash" "fish" "zsh" ];
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("default.target")
+
+    machine.succeed("expect-bash")
+    machine.succeed("expect-fish")
+    machine.succeed("expect-zsh")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/step-ca.nix b/nixpkgs/nixos/tests/step-ca.nix
new file mode 100644
index 000000000000..a855b590232d
--- /dev/null
+++ b/nixpkgs/nixos/tests/step-ca.nix
@@ -0,0 +1,77 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    test-certificates = pkgs.runCommandLocal "test-certificates" { } ''
+      mkdir -p $out
+      echo insecure-root-password > $out/root-password-file
+      echo insecure-intermediate-password > $out/intermediate-password-file
+      ${pkgs.step-cli}/bin/step certificate create "Example Root CA" $out/root_ca.crt $out/root_ca.key --password-file=$out/root-password-file --profile root-ca
+      ${pkgs.step-cli}/bin/step certificate create "Example Intermediate CA 1" $out/intermediate_ca.crt $out/intermediate_ca.key --password-file=$out/intermediate-password-file --ca-password-file=$out/root-password-file --profile intermediate-ca --ca $out/root_ca.crt --ca-key $out/root_ca.key
+    '';
+  in
+  {
+    name = "step-ca";
+    nodes =
+      {
+        caserver =
+          { config, pkgs, ... }: {
+            services.step-ca = {
+              enable = true;
+              address = "0.0.0.0";
+              port = 8443;
+              openFirewall = true;
+              intermediatePasswordFile = "${test-certificates}/intermediate-password-file";
+              settings = {
+                dnsNames = [ "caserver" ];
+                root = "${test-certificates}/root_ca.crt";
+                crt = "${test-certificates}/intermediate_ca.crt";
+                key = "${test-certificates}/intermediate_ca.key";
+                db = {
+                  type = "badger";
+                  dataSource = "/var/lib/step-ca/db";
+                };
+                authority = {
+                  provisioners = [
+                    {
+                      type = "ACME";
+                      name = "acme";
+                    }
+                  ];
+                };
+              };
+            };
+          };
+
+        caclient =
+          { config, pkgs, ... }: {
+            security.acme.defaults.server = "https://caserver:8443/acme/acme/directory";
+            security.acme.defaults.email = "root@example.org";
+            security.acme.acceptTerms = true;
+
+            security.pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
+
+            networking.firewall.allowedTCPPorts = [ 80 443 ];
+
+            services.nginx = {
+              enable = true;
+              virtualHosts = {
+                "caclient" = {
+                  forceSSL = true;
+                  enableACME = true;
+                };
+              };
+            };
+          };
+
+        catester = { config, pkgs, ... }: {
+          security.pki.certificateFiles = [ "${test-certificates}/root_ca.crt" ];
+        };
+      };
+
+    testScript =
+      ''
+        catester.start()
+        caserver.wait_for_unit("step-ca.service")
+        caclient.wait_for_unit("acme-finished-caclient.target")
+        catester.succeed("curl https://caclient/ | grep \"Welcome to nginx!\"")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/stratis/default.nix b/nixpkgs/nixos/tests/stratis/default.nix
new file mode 100644
index 000000000000..42daadd5fcaa
--- /dev/null
+++ b/nixpkgs/nixos/tests/stratis/default.nix
@@ -0,0 +1,8 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+
+{
+  simple = import ./simple.nix { inherit system pkgs; };
+  encryption = import ./encryption.nix { inherit system pkgs; };
+}
diff --git a/nixpkgs/nixos/tests/stratis/encryption.nix b/nixpkgs/nixos/tests/stratis/encryption.nix
new file mode 100644
index 000000000000..81b5f92b4ac4
--- /dev/null
+++ b/nixpkgs/nixos/tests/stratis/encryption.nix
@@ -0,0 +1,32 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  {
+    name = "stratis";
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ nickcao ];
+    };
+
+    nodes.machine = { pkgs, ... }: {
+      services.stratis.enable = true;
+      virtualisation.emptyDiskImages = [ 2048 ];
+    };
+
+    testScript =
+      let
+        testkey1 = pkgs.writeText "testkey1" "supersecret1";
+        testkey2 = pkgs.writeText "testkey2" "supersecret2";
+      in
+      ''
+        machine.wait_for_unit("stratisd")
+        # test creation of encrypted pool and filesystem
+        machine.succeed("stratis key  set    testkey1  --keyfile-path ${testkey1}")
+        machine.succeed("stratis key  set    testkey2  --keyfile-path ${testkey2}")
+        machine.succeed("stratis pool create testpool /dev/vdb --key-desc testkey1")
+        machine.succeed("stratis fs   create testpool testfs")
+        # test rebinding encrypted pool
+        machine.succeed("stratis pool rebind keyring  testpool testkey2")
+        # test restarting encrypted pool
+        machine.succeed("stratis pool stop  --name testpool")
+        machine.succeed("stratis pool start --name testpool --unlock-method keyring")
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/stratis/simple.nix b/nixpkgs/nixos/tests/stratis/simple.nix
new file mode 100644
index 000000000000..543789f59c05
--- /dev/null
+++ b/nixpkgs/nixos/tests/stratis/simple.nix
@@ -0,0 +1,39 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+  {
+    name = "stratis";
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ nickcao ];
+    };
+
+    nodes.machine = { pkgs, ... }: {
+      services.stratis.enable = true;
+      virtualisation.emptyDiskImages = [ 2048 1024 1024 1024 ];
+    };
+
+    testScript = ''
+      machine.wait_for_unit("stratisd")
+      # test pool creation
+      machine.succeed("stratis pool create     testpool /dev/vdb")
+      machine.succeed("stratis pool add-data   testpool /dev/vdc")
+      machine.succeed("stratis pool init-cache testpool /dev/vdd")
+      machine.succeed("stratis pool add-cache  testpool /dev/vde")
+      # test filesystem creation and rename
+      machine.succeed("stratis filesystem create testpool testfs0")
+      machine.succeed("stratis filesystem rename testpool testfs0 testfs1")
+      # test snapshot
+      machine.succeed("mkdir -p /mnt/testfs1 /mnt/testfs2")
+      machine.wait_for_file("/dev/stratis/testpool/testfs1")
+      machine.succeed("mount /dev/stratis/testpool/testfs1 /mnt/testfs1")
+      machine.succeed("echo test0 > /mnt/testfs1/test0")
+      machine.succeed("echo test1 > /mnt/testfs1/test1")
+      machine.succeed("stratis filesystem snapshot testpool testfs1 testfs2")
+      machine.succeed("echo test2 > /mnt/testfs1/test1")
+      machine.wait_for_file("/dev/stratis/testpool/testfs2")
+      machine.succeed("mount /dev/stratis/testpool/testfs2 /mnt/testfs2")
+      assert "test0" in machine.succeed("cat /mnt/testfs1/test0")
+      assert "test0" in machine.succeed("cat /mnt/testfs2/test0")
+      assert "test2" in machine.succeed("cat /mnt/testfs1/test1")
+      assert "test1" in machine.succeed("cat /mnt/testfs2/test1")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/strongswan-swanctl.nix b/nixpkgs/nixos/tests/strongswan-swanctl.nix
new file mode 100644
index 000000000000..0cf181ee62a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/strongswan-swanctl.nix
@@ -0,0 +1,148 @@
+# This strongswan-swanctl test is based on:
+# https://www.strongswan.org/testing/testresults/swanctl/rw-psk-ipv4/index.html
+# https://github.com/strongswan/strongswan/tree/master/testing/tests/swanctl/rw-psk-ipv4
+#
+# The roadwarrior carol sets up a connection to gateway moon. The authentication
+# is based on pre-shared keys and IPv4 addresses. Upon the successful
+# establishment of the IPsec tunnels, the specified updown script automatically
+# inserts iptables-based firewall rules that let pass the tunneled traffic. In
+# order to test both tunnel and firewall, carol pings the client alice behind
+# the gateway moon.
+#
+#     alice                       moon                        carol
+#      eth1------vlan_0------eth1        eth2------vlan_1------eth1
+#   192.168.0.1         192.168.0.3  192.168.1.3           192.168.1.2
+#
+# See the NixOS manual for how to run this test:
+# https://nixos.org/nixos/manual/index.html#sec-running-nixos-tests-interactively
+
+import ./make-test-python.nix ({ pkgs, ...} :
+
+let
+  allowESP = "iptables --insert INPUT --protocol ESP --jump ACCEPT";
+
+  # Shared VPN settings:
+  vlan0         = "192.168.0.0/24";
+  carolIp       = "192.168.1.2";
+  moonIp        = "192.168.1.3";
+  version       = 2;
+  secret        = "0sFpZAZqEN6Ti9sqt4ZP5EWcqx";
+  esp_proposals = [ "aes128gcm128-x25519" ];
+  proposals     = [ "aes128-sha256-x25519" ];
+in {
+  name = "strongswan-swanctl";
+  meta.maintainers = with pkgs.lib.maintainers; [ basvandijk ];
+  nodes = {
+
+    alice = { ... } : {
+      virtualisation.vlans = [ 0 ];
+      networking = {
+        dhcpcd.enable = false;
+        defaultGateway = "192.168.0.3";
+      };
+    };
+
+    moon = { config, ...} :
+      let strongswan = config.services.strongswan-swanctl.package;
+      in {
+        virtualisation.vlans = [ 0 1 ];
+        networking = {
+          dhcpcd.enable = false;
+          firewall = {
+            allowedUDPPorts = [ 4500 500 ];
+            extraCommands = allowESP;
+          };
+          nat = {
+            enable             = true;
+            internalIPs        = [ vlan0 ];
+            internalInterfaces = [ "eth1" ];
+            externalIP         = moonIp;
+            externalInterface  = "eth2";
+          };
+        };
+        environment.systemPackages = [ strongswan ];
+        services.strongswan-swanctl = {
+          enable = true;
+          swanctl = {
+            connections = {
+              rw = {
+                local_addrs = [ moonIp ];
+                local.main = {
+                  auth = "psk";
+                };
+                remote.main = {
+                  auth = "psk";
+                };
+                children = {
+                  net = {
+                    local_ts = [ vlan0 ];
+                    updown = "${strongswan}/libexec/ipsec/_updown iptables";
+                    inherit esp_proposals;
+                  };
+                };
+                inherit version;
+                inherit proposals;
+              };
+            };
+            secrets = {
+              ike.carol = {
+                id.main = carolIp;
+                inherit secret;
+              };
+            };
+          };
+        };
+      };
+
+    carol = { config, ...} :
+      let strongswan = config.services.strongswan-swanctl.package;
+      in {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          dhcpcd.enable = false;
+          firewall.extraCommands = allowESP;
+        };
+        environment.systemPackages = [ strongswan ];
+        services.strongswan-swanctl = {
+          enable = true;
+          swanctl = {
+            connections = {
+              home = {
+                local_addrs = [ carolIp ];
+                remote_addrs = [ moonIp ];
+                local.main = {
+                  auth = "psk";
+                  id = carolIp;
+                };
+                remote.main = {
+                  auth = "psk";
+                  id = moonIp;
+                };
+                children = {
+                  home = {
+                    remote_ts = [ vlan0 ];
+                    start_action = "trap";
+                    updown = "${strongswan}/libexec/ipsec/_updown iptables";
+                    inherit esp_proposals;
+                  };
+                };
+                inherit version;
+                inherit proposals;
+              };
+            };
+            secrets = {
+              ike.moon = {
+                id.main = moonIp;
+                inherit secret;
+              };
+            };
+          };
+        };
+      };
+
+  };
+  testScript = ''
+    start_all()
+    carol.wait_until_succeeds("ping -c 1 alice")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/stub-ld.nix b/nixpkgs/nixos/tests/stub-ld.nix
new file mode 100644
index 000000000000..25161301741b
--- /dev/null
+++ b/nixpkgs/nixos/tests/stub-ld.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "stub-ld";
+
+  nodes.machine = { lib, ... }:
+    {
+      environment.stub-ld.enable = true;
+
+      specialisation.nostub = {
+        inheritParentConfig = true;
+
+        configuration = { ... }: {
+          environment.stub-ld.enable = lib.mkForce false;
+        };
+      };
+    };
+
+  testScript = let
+    libDir = pkgs.stdenv.hostPlatform.libDir;
+    ldsoBasename = lib.last (lib.splitString "/" pkgs.stdenv.cc.bintools.dynamicLinker);
+
+    check32 = pkgs.stdenv.isx86_64;
+    pkgs32 = pkgs.pkgsi686Linux;
+
+    libDir32 = pkgs32.stdenv.hostPlatform.libDir;
+    ldsoBasename32 = lib.last (lib.splitString "/" pkgs32.stdenv.cc.bintools.dynamicLinker);
+
+    test-exec = builtins.mapAttrs (n: v: pkgs.runCommand "test-exec-${n}" { src = pkgs.fetchurl v; } "mkdir -p $out;cd $out;tar -xzf $src") {
+      x86_64-linux.url = "https://github.com/rustic-rs/rustic/releases/download/v0.6.1/rustic-v0.6.1-x86_64-unknown-linux-gnu.tar.gz";
+      x86_64-linux.hash = "sha256-3zySzx8MKFprMOi++yr2ZGASE0aRfXHQuG3SN+kWUCI=";
+      i686-linux.url = "https://github.com/rustic-rs/rustic/releases/download/v0.6.1/rustic-v0.6.1-i686-unknown-linux-gnu.tar.gz";
+      i686-linux.hash = "sha256-fWNiATFeg0B2pfB5zndlnzGn7Ztl8diVS1rFLEDnSLU=";
+      aarch64-linux.url = "https://github.com/rustic-rs/rustic/releases/download/v0.6.1/rustic-v0.6.1-aarch64-unknown-linux-gnu.tar.gz";
+      aarch64-linux.hash = "sha256-hnldbd2cctQIAhIKoEZLIWY8H3jiFBClkNy2UlyyvAs=";
+    };
+    exec-name = "rustic";
+
+    if32 = pythonStatement: if check32 then pythonStatement else "pass";
+  in
+    ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      with subtest("Check for stub (enabled, initial)"):
+          machine.succeed('test -L /${libDir}/${ldsoBasename}')
+          ${if32 "machine.succeed('test -L /${libDir32}/${ldsoBasename32}')"}
+
+      with subtest("Try FHS executable"):
+          machine.copy_from_host('${test-exec.${pkgs.system}}','test-exec')
+          machine.succeed('if test-exec/${exec-name} 2>outfile; then false; else [ $? -eq 127 ];fi')
+          machine.succeed('grep -qi nixos outfile')
+          ${if32 "machine.copy_from_host('${test-exec.${pkgs32.system}}','test-exec32')"}
+          ${if32 "machine.succeed('if test-exec32/${exec-name} 2>outfile32; then false; else [ $? -eq 127 ];fi')"}
+          ${if32 "machine.succeed('grep -qi nixos outfile32')"}
+
+      with subtest("Disable stub"):
+          machine.succeed("/run/booted-system/specialisation/nostub/bin/switch-to-configuration test")
+
+      with subtest("Check for stub (disabled)"):
+          machine.fail('test -e /${libDir}/${ldsoBasename}')
+          ${if32 "machine.fail('test -e /${libDir32}/${ldsoBasename32}')"}
+
+      with subtest("Create file in stub location (to be overwritten)"):
+          machine.succeed('mkdir -p /${libDir};touch /${libDir}/${ldsoBasename}')
+          ${if32 "machine.succeed('mkdir -p /${libDir32};touch /${libDir32}/${ldsoBasename32}')"}
+
+      with subtest("Re-enable stub"):
+          machine.succeed("/run/booted-system/bin/switch-to-configuration test")
+
+      with subtest("Check for stub (enabled, final)"):
+          machine.succeed('test -L /${libDir}/${ldsoBasename}')
+          ${if32 "machine.succeed('test -L /${libDir32}/${ldsoBasename32}')"}
+    '';
+})
diff --git a/nixpkgs/nixos/tests/stunnel.nix b/nixpkgs/nixos/tests/stunnel.nix
new file mode 100644
index 000000000000..f8cfa0414761
--- /dev/null
+++ b/nixpkgs/nixos/tests/stunnel.nix
@@ -0,0 +1,181 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  stunnelCommon = {
+    services.stunnel = {
+      enable = true;
+      user = "stunnel";
+    };
+    users.groups.stunnel = { };
+    users.users.stunnel = {
+      isSystemUser = true;
+      group = "stunnel";
+    };
+  };
+  makeCert = { config, pkgs, ... }: {
+    systemd.services.create-test-cert = {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.Type = "oneshot";
+      script = ''
+        ${pkgs.openssl}/bin/openssl req -batch -x509 -newkey rsa -nodes -out /test-cert.pem -keyout /test-key.pem -subj /CN=${config.networking.hostName}
+        ( umask 077; cat /test-key.pem /test-cert.pem > /test-key-and-cert.pem )
+        chown stunnel /test-key.pem /test-key-and-cert.pem
+    '';
+    };
+  };
+  serverCommon = { pkgs, ... }: {
+    networking.firewall.allowedTCPPorts = [ 443 ];
+    services.stunnel.servers.https = {
+      accept = "443";
+      connect = 80;
+      cert = "/test-key-and-cert.pem";
+    };
+    systemd.services.simple-webserver = {
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        cd /etc/webroot
+        ${pkgs.python3}/bin/python -m http.server 80
+      '';
+    };
+  };
+  copyCert = src: dest: filename: ''
+    from shlex import quote
+    ${src}.wait_for_file("/test-key-and-cert.pem")
+    server_cert = ${src}.succeed("cat /test-cert.pem")
+    ${dest}.succeed("echo %s > ${filename}" % quote(server_cert))
+  '';
+
+in {
+  basicServer = makeTest {
+    name = "basicServer";
+
+    nodes = {
+      client = { };
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        environment.etc."webroot/index.html".text = "well met";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+
+      server.wait_for_unit("simple-webserver")
+      server.wait_for_unit("stunnel")
+
+      client.succeed("curl --fail --cacert /authorized-server-cert.crt https://server/ > out")
+      client.succeed('[[ "$(< out)" == "well met" ]]')
+    '';
+  };
+
+  serverAndClient = makeTest {
+    name = "serverAndClient";
+
+    nodes = {
+      client = {
+        imports = [ stunnelCommon ];
+        services.stunnel.clients = {
+          httpsClient = {
+            accept = "80";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+          };
+          httpsClientWithHostVerify = {
+            accept = "81";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+            verifyHostname = "server";
+          };
+          httpsClientWithHostVerifyFail = {
+            accept = "82";
+            connect = "server:443";
+            CAFile = "/authorized-server-cert.crt";
+            verifyHostname = "wronghostname";
+          };
+        };
+      };
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        environment.etc."webroot/index.html".text = "hello there";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+
+      server.wait_for_unit("simple-webserver")
+      server.wait_for_unit("stunnel")
+
+      # In case stunnel came up before we got the server's cert copied over
+      client.succeed("systemctl reload-or-restart stunnel")
+
+      client.succeed("curl --fail http://localhost/ > out")
+      client.succeed('[[ "$(< out)" == "hello there" ]]')
+
+      client.succeed("curl --fail http://localhost:81/ > out")
+      client.succeed('[[ "$(< out)" == "hello there" ]]')
+
+      client.fail("curl --fail http://localhost:82/ > out")
+      client.succeed('[[ "$(< out)" == "" ]]')
+    '';
+  };
+
+  mutualAuth = makeTest {
+    name = "mutualAuth";
+
+    nodes = rec {
+      client = {
+        imports = [ makeCert stunnelCommon ];
+        services.stunnel.clients.authenticated-https = {
+          accept = "80";
+          connect = "server:443";
+          verifyPeer = true;
+          CAFile = "/authorized-server-cert.crt";
+          cert = "/test-cert.pem";
+          key = "/test-key.pem";
+        };
+      };
+      wrongclient = client;
+      server = {
+        imports = [ makeCert serverCommon stunnelCommon ];
+        services.stunnel.servers.https = {
+          CAFile = "/authorized-client-certs.crt";
+          verifyPeer = true;
+        };
+        environment.etc."webroot/index.html".text = "secret handshake";
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${copyCert "server" "client" "/authorized-server-cert.crt"}
+      ${copyCert "client" "server" "/authorized-client-certs.crt"}
+      ${copyCert "server" "wrongclient" "/authorized-server-cert.crt"}
+
+      # In case stunnel came up before we got the cross-certs in place
+      client.succeed("systemctl reload-or-restart stunnel")
+      server.succeed("systemctl reload-or-restart stunnel")
+      wrongclient.succeed("systemctl reload-or-restart stunnel")
+
+      server.wait_for_unit("simple-webserver")
+      client.fail("curl --fail --insecure https://server/ > out")
+      client.succeed('[[ "$(< out)" == "" ]]')
+      client.succeed("curl --fail http://localhost/ > out")
+      client.succeed('[[ "$(< out)" == "secret handshake" ]]')
+      wrongclient.fail("curl --fail http://localhost/ > out")
+      wrongclient.succeed('[[ "$(< out)" == "" ]]')
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/sudo-rs.nix b/nixpkgs/nixos/tests/sudo-rs.nix
new file mode 100644
index 000000000000..753e00686e95
--- /dev/null
+++ b/nixpkgs/nixos/tests/sudo-rs.nix
@@ -0,0 +1,95 @@
+# Some tests to ensure sudo is working properly.
+{ pkgs, ... }:
+let
+  inherit (pkgs.lib) mkIf optionalString;
+  password = "helloworld";
+in
+  import ./make-test-python.nix ({ lib, pkgs, ...} : {
+    name = "sudo-rs";
+    meta.maintainers = pkgs.sudo-rs.meta.maintainers;
+
+    nodes.machine =
+      { lib, ... }:
+      {
+        environment.systemPackages = [ pkgs.faketty ];
+        users.groups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
+        users.users = {
+          test0 = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+          test1 = { isNormalUser = true; password = password; };
+          test2 = { isNormalUser = true; extraGroups = [ "foobar" ]; password = password; };
+          test3 = { isNormalUser = true; extraGroups = [ "barfoo" ]; };
+          test4 = { isNormalUser = true; extraGroups = [ "baz" ]; };
+          test5 = { isNormalUser = true; };
+        };
+
+        security.sudo-rs = {
+          enable = true;
+          wheelNeedsPassword = false;
+
+          extraRules = [
+            # SUDOERS SYNTAX CHECK (Test whether the module produces a valid output;
+            # errors being detected by the visudo checks.
+
+            # These should not create any entries
+            { users = [ "notest1" ]; commands = [ ]; }
+            { commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+            # Test defining commands with the options syntax, though not setting any options
+            { users = [ "notest2" ]; commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+
+            # CONFIGURATION FOR TEST CASES
+            { users = [ "test1" ]; groups = [ "foobar" ]; commands = [ "ALL" ]; }
+            { groups = [ "barfoo" 1337 ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
+            { users = [ "test5" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; runAs = "test1:barfoo"; }
+          ];
+        };
+      };
+
+    nodes.strict = { ... }: {
+      environment.systemPackages = [ pkgs.faketty ];
+      users.users = {
+        admin = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+        noadmin = { isNormalUser = true; };
+      };
+
+      security.sudo-rs = {
+        enable = true;
+        wheelNeedsPassword = false;
+        execWheelOnly = true;
+      };
+    };
+
+    testScript =
+      ''
+        with subtest("users in wheel group should have passwordless sudo"):
+            machine.succeed('faketty -- su - test0 -c "sudo -u root true"')
+
+        with subtest("test1 user should have sudo with password"):
+            machine.succeed('faketty -- su - test1 -c "echo ${password} | sudo -S -u root true"')
+
+        with subtest("test1 user should not be able to use sudo without password"):
+            machine.fail('faketty -- su - test1 -c "sudo -n -u root true"')
+
+        with subtest("users in group 'foobar' should be able to use sudo with password"):
+            machine.succeed('faketty -- su - test2 -c "echo ${password} | sudo -S -u root true"')
+
+        with subtest("users in group 'barfoo' should be able to use sudo without password"):
+            machine.succeed("sudo -u test3 sudo -n -u root true")
+
+        with subtest("users in group 'baz' (GID 1337)"):
+            machine.succeed("sudo -u test4 sudo -n -u root echo true")
+
+        with subtest("test5 user should be able to run commands under test1"):
+            machine.succeed("sudo -u test5 sudo -n -u test1 true")
+
+        with subtest("test5 user should not be able to run commands under root"):
+            machine.fail("sudo -u test5 sudo -n -u root true 2>/dev/null")
+
+        with subtest("users in wheel should be able to run sudo despite execWheelOnly"):
+            strict.succeed('faketty -- su - admin -c "sudo -u root true"')
+
+        with subtest("non-wheel users should be unable to run sudo thanks to execWheelOnly"):
+            strict.fail('faketty -- su - noadmin -c "sudo --help"')
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/sudo.nix b/nixpkgs/nixos/tests/sudo.nix
new file mode 100644
index 000000000000..1fe478f0bff1
--- /dev/null
+++ b/nixpkgs/nixos/tests/sudo.nix
@@ -0,0 +1,103 @@
+# Some tests to ensure sudo is working properly.
+
+let
+  password = "helloworld";
+in
+  import ./make-test-python.nix ({ lib, pkgs, ...} : {
+    name = "sudo";
+    meta.maintainers = pkgs.sudo.meta.maintainers;
+
+    nodes.machine =
+      { lib, ... }:
+      {
+        users.groups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
+        users.users = {
+          test0 = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+          test1 = { isNormalUser = true; password = password; };
+          test2 = { isNormalUser = true; extraGroups = [ "foobar" ]; password = password; };
+          test3 = { isNormalUser = true; extraGroups = [ "barfoo" ]; };
+          test4 = { isNormalUser = true; extraGroups = [ "baz" ]; };
+          test5 = { isNormalUser = true; };
+        };
+
+        security.sudo = {
+          # Explicitly _not_ defining 'enable = true;' here, to check that sudo is enabled by default
+
+          wheelNeedsPassword = false;
+
+          extraConfig = ''
+            Defaults lecture="never"
+          '';
+
+          extraRules = [
+            # SUDOERS SYNTAX CHECK (Test whether the module produces a valid output;
+            # errors being detected by the visudo checks.
+
+            # These should not create any entries
+            { users = [ "notest1" ]; commands = [ ]; }
+            { commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+            # Test defining commands with the options syntax, though not setting any options
+            { users = [ "notest2" ]; commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+
+            # CONFIGURATION FOR TEST CASES
+            { users = [ "test1" ]; groups = [ "foobar" ]; commands = [ "ALL" ]; }
+            { groups = [ "barfoo" 1337 ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" "NOSETENV" ]; } ]; }
+            { users = [ "test5" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" "SETENV" ]; } ]; runAs = "test1:barfoo"; }
+          ];
+        };
+      };
+
+    nodes.strict = { ... }: {
+      users.users = {
+        admin = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+        noadmin = { isNormalUser = true; };
+      };
+
+      security.sudo = {
+        enable = true;
+        wheelNeedsPassword = false;
+        execWheelOnly = true;
+      };
+    };
+
+    testScript =
+      ''
+        with subtest("users in wheel group should have passwordless sudo"):
+            machine.succeed('su - test0 -c "sudo -u root true"')
+
+        with subtest("test1 user should have sudo with password"):
+            machine.succeed('su - test1 -c "echo ${password} | sudo -S -u root true"')
+
+        with subtest("test1 user should not be able to use sudo without password"):
+            machine.fail('su - test1 -c "sudo -n -u root true"')
+
+        with subtest("users in group 'foobar' should be able to use sudo with password"):
+            machine.succeed('su - test2 -c "echo ${password} | sudo -S -u root true"')
+
+        with subtest("users in group 'barfoo' should be able to use sudo without password"):
+            machine.succeed("sudo -u test3 sudo -n -u root true")
+
+        with subtest("users in group 'baz' (GID 1337)"):
+            machine.succeed("sudo -u test4 sudo -n -u root echo true")
+
+        with subtest("test5 user should be able to run commands under test1"):
+            machine.succeed("sudo -u test5 sudo -n -u test1 true")
+
+        with subtest("test5 user should not be able to run commands under root"):
+            machine.fail("sudo -u test5 sudo -n -u root true")
+
+        with subtest("test5 user should be able to keep their environment"):
+            machine.succeed("sudo -u test5 sudo -n -E -u test1 true")
+
+        with subtest("users in group 'barfoo' should not be able to keep their environment"):
+            machine.fail("sudo -u test3 sudo -n -E -u root true")
+
+        with subtest("users in wheel should be able to run sudo despite execWheelOnly"):
+            strict.succeed('su - admin -c "sudo -u root true"')
+
+        with subtest("non-wheel users should be unable to run sudo thanks to execWheelOnly"):
+            strict.fail('su - noadmin -c "sudo --help"')
+      '';
+  })
diff --git a/nixpkgs/nixos/tests/suwayomi-server.nix b/nixpkgs/nixos/tests/suwayomi-server.nix
new file mode 100644
index 000000000000..36072028380b
--- /dev/null
+++ b/nixpkgs/nixos/tests/suwayomi-server.nix
@@ -0,0 +1,46 @@
+{ system ? builtins.currentSystem
+, pkgs
+, lib ? pkgs.lib
+}:
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (lib) recursiveUpdate;
+
+  baseTestConfig = {
+    meta.maintainers = with lib.maintainers; [ ratcornu ];
+    nodes.machine = { pkgs, ... }: {
+      services.suwayomi-server = {
+        enable = true;
+        settings.server.port = 1234;
+      };
+    };
+    testScript = ''
+      machine.wait_for_unit("suwayomi-server.service")
+      machine.wait_for_open_port(1234)
+      machine.succeed("curl --fail http://localhost:1234/")
+    '';
+  };
+in
+
+{
+  without-auth = makeTest (recursiveUpdate baseTestConfig {
+    name = "suwayomi-server-without-auth";
+  });
+
+  with-auth = makeTest (recursiveUpdate baseTestConfig {
+    name = "suwayomi-server-with-auth";
+
+    nodes.machine = { pkgs, ... }: {
+      services.suwayomi-server = {
+        enable = true;
+
+        settings.server = {
+          port = 1234;
+          basicAuthEnabled = true;
+          basicAuthUsername = "alice";
+          basicAuthPasswordFile = pkgs.writeText "snakeoil-pass.txt" "pass";
+        };
+      };
+    };
+  });
+}
diff --git a/nixpkgs/nixos/tests/swap-file-btrfs.nix b/nixpkgs/nixos/tests/swap-file-btrfs.nix
new file mode 100644
index 000000000000..35b9fb4fa50a
--- /dev/null
+++ b/nixpkgs/nixos/tests/swap-file-btrfs.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "swap-file-btrfs";
+
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      virtualisation.useDefaultFilesystems = false;
+
+      virtualisation.rootDevice = "/dev/vda";
+
+      boot.initrd.postDeviceCommands = ''
+        ${pkgs.btrfs-progs}/bin/mkfs.btrfs --label root /dev/vda
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-label/root";
+          fsType = "btrfs";
+        };
+      };
+
+      swapDevices = [
+        {
+          device = "/var/swapfile";
+          size = 1; # 1MiB.
+        }
+      ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit('var-swapfile.swap')
+    # Ensure the swap file creation script ran to completion without failing when creating the swap file
+    machine.fail("systemctl is-failed --quiet mkswap-var-swapfile.service")
+    machine.succeed("stat --file-system --format=%T /var/swapfile | grep btrfs")
+    # First run. Auto creation.
+    machine.succeed("swapon --show | grep /var/swapfile")
+
+    machine.shutdown()
+    machine.start()
+
+    # Second run. Use it as-is.
+    machine.wait_for_unit('var-swapfile.swap')
+    # Ensure the swap file creation script ran to completion without failing when the swap file already exists
+    machine.fail("systemctl is-failed --quiet mkswap-var-swapfile.service")
+    machine.succeed("swapon --show | grep /var/swapfile")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/swap-partition.nix b/nixpkgs/nixos/tests/swap-partition.nix
new file mode 100644
index 000000000000..ddcaeb95453e
--- /dev/null
+++ b/nixpkgs/nixos/tests/swap-partition.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "swap-partition";
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    {
+      virtualisation.useDefaultFilesystems = false;
+
+      virtualisation.rootDevice = "/dev/vda1";
+
+      boot.initrd.postDeviceCommands = ''
+        if ! test -b /dev/vda1; then
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mklabel msdos
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary 1MiB -250MiB
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary -250MiB 100%
+          sync
+        fi
+
+        FSTYPE=$(blkid -o value -s TYPE /dev/vda1 || true)
+        if test -z "$FSTYPE"; then
+          ${pkgs.e2fsprogs}/bin/mke2fs -t ext4 -L root /dev/vda1
+          ${pkgs.util-linux}/bin/mkswap --label swap /dev/vda2
+        fi
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-label/root";
+          fsType = "ext4";
+        };
+      };
+
+      swapDevices = [
+        {
+          device = "/dev/disk/by-label/swap";
+        }
+      ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Swap is active"):
+      # Doesn't matter if the numbers reported by `free` are slightly off due to unit conversions.
+      machine.succeed("free -h | grep -E 'Swap:\s+2[45][0-9]Mi'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/swap-random-encryption.nix b/nixpkgs/nixos/tests/swap-random-encryption.nix
new file mode 100644
index 000000000000..9e919db65dde
--- /dev/null
+++ b/nixpkgs/nixos/tests/swap-random-encryption.nix
@@ -0,0 +1,80 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "swap-random-encryption";
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    {
+      environment.systemPackages = [ pkgs.cryptsetup ];
+
+      virtualisation.useDefaultFilesystems = false;
+
+      virtualisation.rootDevice = "/dev/vda1";
+
+      boot.initrd.postDeviceCommands = ''
+        if ! test -b /dev/vda1; then
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mklabel msdos
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary 1MiB -250MiB
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary -250MiB 100%
+          sync
+        fi
+
+        FSTYPE=$(blkid -o value -s TYPE /dev/vda1 || true)
+        if test -z "$FSTYPE"; then
+          ${pkgs.e2fsprogs}/bin/mke2fs -t ext4 -L root /dev/vda1
+        fi
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-label/root";
+          fsType = "ext4";
+        };
+      };
+
+      swapDevices = [
+        {
+          device = "/dev/vda2";
+
+          randomEncryption = {
+            enable = true;
+            cipher = "aes-xts-plain64";
+            keySize = 512;
+            sectorSize = 4096;
+          };
+        }
+      ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Swap is active"):
+      # Doesn't matter if the numbers reported by `free` are slightly off due to unit conversions.
+      machine.succeed("free -h | grep -E 'Swap:\s+2[45][0-9]Mi'")
+
+    with subtest("Swap device has 4k sector size"):
+      import json
+      result = json.loads(machine.succeed("lsblk -Jo PHY-SEC,LOG-SEC /dev/mapper/dev-vda2"))
+      block_devices = result["blockdevices"]
+      if len(block_devices) != 1:
+        raise Exception ("lsblk output did not report exactly one block device")
+
+      swapDevice = block_devices[0];
+      if not (swapDevice["phy-sec"] == 4096 and swapDevice["log-sec"] == 4096):
+        raise Exception ("swap device does not have the sector size specified in the configuration")
+
+    with subtest("Swap encrypt has assigned cipher and keysize"):
+      import re
+
+      results = machine.succeed("cryptsetup status dev-vda2").splitlines()
+
+      cipher_pattern = re.compile(r"\s*cipher:\s+aes-xts-plain64\s*")
+      if not any(cipher_pattern.fullmatch(line) for line in results):
+        raise Exception ("swap device encryption does not use the cipher specified in the configuration")
+
+      key_size_pattern = re.compile(r"\s*keysize:\s+512\s+bits\s*")
+      if not any(key_size_pattern.fullmatch(line) for line in results):
+        raise Exception ("swap device encryption does not use the key size specified in the configuration")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sway.nix b/nixpkgs/nixos/tests/sway.nix
new file mode 100644
index 000000000000..185c5b1b0aa9
--- /dev/null
+++ b/nixpkgs/nixos/tests/sway.nix
@@ -0,0 +1,193 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "sway";
+  meta = {
+    maintainers = with lib.maintainers; [ primeos synthetica ];
+  };
+
+  # testScriptWithTypes:49: error: Cannot call function of unknown type
+  #           (machine.succeed if succeed else machine.execute)(
+  #           ^
+  # Found 1 error in 1 file (checked 1 source file)
+  skipTypeCheck = true;
+
+  nodes.machine = { config, ... }: {
+    # Automatically login on tty1 as a normal user:
+    imports = [ ./common/user-account.nix ];
+    services.getty.autologinUser = "alice";
+
+    environment = {
+      # For glinfo and wayland-info:
+      systemPackages = with pkgs; [ mesa-demos wayland-utils alacritty ];
+      # Use a fixed SWAYSOCK path (for swaymsg):
+      variables = {
+        "SWAYSOCK" = "/tmp/sway-ipc.sock";
+        # TODO: Investigate if we can get hardware acceleration to work (via
+        # virtio-gpu and Virgil). We currently have to use the Pixman software
+        # renderer since the GLES2 renderer doesn't work inside the VM (even
+        # with WLR_RENDERER_ALLOW_SOFTWARE):
+        # "WLR_RENDERER_ALLOW_SOFTWARE" = "1";
+        "WLR_RENDERER" = "pixman";
+      };
+      # For convenience:
+      shellAliases = {
+        test-x11 = "glinfo | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok";
+        test-wayland = "wayland-info | tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok";
+      };
+
+      # To help with OCR:
+      etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
+        main = {
+          font = "inconsolata:size=14";
+        };
+        colors = rec {
+          foreground = "000000";
+          background = "ffffff";
+          regular2 = foreground;
+        };
+      };
+
+      etc."gpg-agent.conf".text = ''
+        pinentry-timeout 86400
+      '';
+    };
+
+    fonts.packages = [ pkgs.inconsolata ];
+
+    # Automatically configure and start Sway when logging in on tty1:
+    programs.bash.loginShellInit = ''
+      if [ "$(tty)" = "/dev/tty1" ]; then
+        set -e
+
+        mkdir -p ~/.config/sway
+        sed s/Mod4/Mod1/ /etc/sway/config > ~/.config/sway/config
+
+        sway --validate
+        sway && touch /tmp/sway-exit-ok
+      fi
+    '';
+
+    programs.sway.enable = true;
+
+    # To test pinentry via gpg-agent:
+    programs.gnupg.agent.enable = true;
+
+    # Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
+    virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+  };
+
+  testScript = { nodes, ... }: ''
+    import shlex
+    import json
+
+    q = shlex.quote
+    NODE_GROUPS = ["nodes", "floating_nodes"]
+
+
+    def swaymsg(command: str = "", succeed=True, type="command"):
+        assert command != "" or type != "command", "Must specify command or type"
+        shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
+        with machine.nested(
+            f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
+        ):
+            ret = (machine.succeed if succeed else machine.execute)(
+                f"su - alice -c {shell}"
+            )
+
+        # execute also returns a status code, but disregard.
+        if not succeed:
+            _, ret = ret
+
+        if not succeed and not ret:
+            return None
+
+        parsed = json.loads(ret)
+        return parsed
+
+
+    def walk(tree):
+        yield tree
+        for group in NODE_GROUPS:
+            for node in tree.get(group, []):
+                yield from walk(node)
+
+
+    def wait_for_window(pattern):
+        def func(last_chance):
+            nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
+
+            if last_chance:
+                nodes = list(nodes)
+                machine.log(f"Last call! Current list of windows: {nodes}")
+
+            return any(pattern in name for name in nodes)
+
+        retry(func)
+
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+
+    # To check the version:
+    print(machine.succeed("sway --version"))
+
+    # Wait for Sway to complete startup:
+    machine.wait_for_file("/run/user/1000/wayland-1")
+    machine.wait_for_file("/tmp/sway-ipc.sock")
+
+    # Test XWayland (foot does not support X):
+    swaymsg("exec WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= alacritty")
+    wait_for_window("alice@machine")
+    machine.send_chars("test-x11\n")
+    machine.wait_for_file("/tmp/test-x11-exit-ok")
+    print(machine.succeed("cat /tmp/test-x11.out"))
+    machine.copy_from_vm("/tmp/test-x11.out")
+    machine.screenshot("alacritty_glinfo")
+    machine.succeed("pkill alacritty")
+
+    # Start a terminal (foot) on workspace 3:
+    machine.send_key("alt-3")
+    machine.sleep(3)
+    machine.send_key("alt-ret")
+    wait_for_window("alice@machine")
+    machine.send_chars("test-wayland\n")
+    machine.wait_for_file("/tmp/test-wayland-exit-ok")
+    print(machine.succeed("cat /tmp/test-wayland.out"))
+    machine.copy_from_vm("/tmp/test-wayland.out")
+    machine.screenshot("foot_wayland_info")
+    machine.send_key("alt-shift-q")
+    machine.wait_until_fails("pgrep foot")
+
+    # Test gpg-agent starting pinentry-gnome3 via D-Bus (tests if
+    # $WAYLAND_DISPLAY is correctly imported into the D-Bus user env):
+    swaymsg("exec mkdir -p ~/.gnupg")
+    swaymsg("exec cp /etc/gpg-agent.conf ~/.gnupg")
+
+    swaymsg("exec DISPLAY=INVALID gpg --no-tty --yes --quick-generate-key test", succeed=False)
+    machine.wait_until_succeeds("pgrep --exact gpg")
+    wait_for_window("gpg")
+    machine.succeed("pgrep --exact gpg")
+    machine.screenshot("gpg_pinentry")
+    machine.send_key("alt-shift-q")
+    machine.wait_until_fails("pgrep --exact gpg")
+
+    # Test swaynag:
+    def get_height():
+        return [node['rect']['height'] for node in walk(swaymsg(type="get_tree")) if node['focused']][0]
+
+    before = get_height()
+    machine.send_key("alt-shift-e")
+    retry(lambda _: get_height() < before)
+    machine.screenshot("sway_exit")
+
+    swaymsg("exec swaylock")
+    machine.wait_until_succeeds("pgrep -x swaylock")
+    machine.sleep(3)
+    machine.send_chars("${nodes.machine.config.users.users.alice.password}")
+    machine.send_key("ret")
+    machine.wait_until_fails("pgrep -x swaylock")
+
+    # Exit Sway and verify process exit status 0:
+    swaymsg("exit", succeed=False)
+    machine.wait_until_fails("pgrep -x sway")
+    machine.wait_for_file("/tmp/sway-exit-ok")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/switch-test.nix b/nixpkgs/nixos/tests/switch-test.nix
new file mode 100644
index 000000000000..5ffdf180d5e3
--- /dev/null
+++ b/nixpkgs/nixos/tests/switch-test.nix
@@ -0,0 +1,1409 @@
+# Test configuration switching.
+
+import ./make-test-python.nix ({ lib, pkgs, ...} : let
+
+  # Simple service that can either be socket-activated or that will
+  # listen on port 1234 if not socket-activated.
+  # A connection to the socket causes 'hello' to be written to the client.
+  socketTest = pkgs.writeScript "socket-test.py" /* python */ ''
+    #!${pkgs.python3}/bin/python3
+
+    from socketserver import TCPServer, StreamRequestHandler
+    import socket
+    import os
+
+
+    class Handler(StreamRequestHandler):
+        def handle(self):
+            self.wfile.write("hello".encode("utf-8"))
+
+
+    class Server(TCPServer):
+        def __init__(self, server_address, handler_cls):
+            listenFds = os.getenv('LISTEN_FDS')
+            if listenFds is None or int(listenFds) < 1:
+                print(f'Binding to {server_address}')
+                TCPServer.__init__(
+                        self, server_address, handler_cls, bind_and_activate=True)
+            else:
+                TCPServer.__init__(
+                        self, server_address, handler_cls, bind_and_activate=False)
+                # Override socket
+                print(f'Got activated by {os.getenv("LISTEN_FDNAMES")} '
+                      f'with {listenFds} FDs')
+                self.socket = socket.fromfd(3, self.address_family,
+                                            self.socket_type)
+
+
+    if __name__ == "__main__":
+        server = Server(("localhost", 1234), Handler)
+        server.serve_forever()
+  '';
+
+in {
+  name = "switch-test";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ gleber das_j ];
+  };
+
+  nodes = {
+    machine = { pkgs, lib, ... }: {
+      environment.systemPackages = [ pkgs.socat ]; # for the socket activation stuff
+      users.mutableUsers = false;
+
+      # For boot/switch testing
+      system.build.installBootLoader = lib.mkForce (pkgs.writeShellScript "install-dummy-loader" ''
+        echo "installing dummy bootloader"
+        touch /tmp/bootloader-installed
+      '');
+
+      specialisation = rec {
+        brokenInitInterface.configuration.config.system.extraSystemBuilderCmds = ''
+          echo "systemd 0" > $out/init-interface-version
+        '';
+
+        modifiedSystemConf.configuration.systemd.extraConfig = ''
+          # Hello world!
+        '';
+
+        addedMount.configuration.virtualisation.fileSystems."/test" = {
+          device = "tmpfs";
+          fsType = "tmpfs";
+        };
+
+        addedMountOptsModified.configuration = {
+          imports = [ addedMount.configuration ];
+          virtualisation.fileSystems."/test".options = [ "x-test" ];
+        };
+
+        addedMountDevModified.configuration = {
+          imports = [ addedMountOptsModified.configuration ];
+          virtualisation.fileSystems."/test".device = lib.mkForce "ramfs";
+        };
+
+        storeMountModified.configuration = {
+          virtualisation.fileSystems."/".device = lib.mkForce "auto";
+        };
+
+        swap.configuration.swapDevices = lib.mkVMOverride [
+          { device = "/swapfile"; size = 1; }
+        ];
+
+        simpleService.configuration = {
+          systemd.services.test = {
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+              ExecReload = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        simpleServiceSeparateActivationScript.configuration = {
+          system.activatable = false;
+          systemd.services.test = {
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+              ExecReload = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        simpleServiceDifferentDescription.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test.description = "Test unit";
+        };
+
+        simpleServiceModified.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test.serviceConfig.X-Test = true;
+        };
+
+        simpleServiceNostop.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test.stopIfChanged = false;
+        };
+
+        simpleServiceReload.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test = {
+            reloadIfChanged = true;
+            serviceConfig.ExecReload = "${pkgs.coreutils}/bin/true";
+          };
+        };
+
+        simpleServiceNorestart.configuration = {
+          imports = [ simpleService.configuration ];
+          systemd.services.test.restartIfChanged = false;
+        };
+
+        simpleServiceFailing.configuration = {
+          imports = [ simpleServiceModified.configuration ];
+          systemd.services.test.serviceConfig.ExecStart = lib.mkForce "${pkgs.coreutils}/bin/false";
+        };
+
+        autorestartService.configuration = {
+          # A service that immediately goes into restarting (but without failing)
+          systemd.services.autorestart = {
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "simple";
+              Restart = "always";
+              RestartSec = "20y"; # Should be long enough
+              ExecStart = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        autorestartServiceFailing.configuration = {
+          imports = [ autorestartService.configuration ];
+          systemd.services.autorestart.serviceConfig = {
+            ExecStart = lib.mkForce "${pkgs.coreutils}/bin/false";
+          };
+        };
+
+        simpleServiceWithExtraSection.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.packages = [ (pkgs.writeTextFile {
+            name = "systemd-extra-section";
+            destination = "/etc/systemd/system/test.service";
+            text = ''
+              [X-Test]
+              X-Test-Value=a
+            '';
+          }) ];
+        };
+
+        simpleServiceWithExtraSectionOtherName.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.packages = [ (pkgs.writeTextFile {
+            name = "systemd-extra-section";
+            destination = "/etc/systemd/system/test.service";
+            text = ''
+              [X-Test2]
+              X-Test-Value=a
+            '';
+          }) ];
+        };
+
+        simpleServiceWithInstallSection.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.packages = [ (pkgs.writeTextFile {
+            name = "systemd-extra-section";
+            destination = "/etc/systemd/system/test.service";
+            text = ''
+              [Install]
+              WantedBy=multi-user.target
+            '';
+          }) ];
+        };
+
+        simpleServiceWithExtraKey.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.services.test.serviceConfig."X-Test" = "test";
+        };
+
+        simpleServiceWithExtraKeyOtherValue.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.services.test.serviceConfig."X-Test" = "test2";
+        };
+
+        simpleServiceWithExtraKeyOtherName.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.services.test.serviceConfig."X-Test2" = "test";
+        };
+
+        simpleServiceReloadTrigger.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.services.test.reloadTriggers = [ "/dev/null" ];
+        };
+
+        simpleServiceReloadTriggerModified.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.services.test.reloadTriggers = [ "/dev/zero" ];
+        };
+
+        simpleServiceReloadTriggerModifiedAndSomethingElse.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.services.test = {
+            reloadTriggers = [ "/dev/zero" ];
+            serviceConfig."X-Test" = "test";
+          };
+        };
+
+        simpleServiceReloadTriggerModifiedSomethingElse.configuration = {
+          imports = [ simpleServiceNostop.configuration ];
+          systemd.services.test.serviceConfig."X-Test" = "test";
+        };
+
+        unitWithBackslash.configuration = {
+          systemd.services."escaped\\x2ddash" = {
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+              ExecReload = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        unitWithBackslashModified.configuration = {
+          imports = [ unitWithBackslash.configuration ];
+          systemd.services."escaped\\x2ddash".serviceConfig.X-Test = "test";
+        };
+
+        unitStartingWithDash.configuration = {
+          systemd.services."-" = {
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        unitStartingWithDashModified.configuration = {
+          imports = [ unitStartingWithDash.configuration ];
+          systemd.services."-" = {
+            reloadIfChanged = true;
+            serviceConfig.ExecReload = "${pkgs.coreutils}/bin/true";
+          };
+        };
+
+        unitWithRequirement.configuration = {
+          systemd.services.required-service = {
+            wantedBy = [ "multi-user.target" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+              ExecReload = "${pkgs.coreutils}/bin/true";
+            };
+          };
+          systemd.services.test-service = {
+            wantedBy = [ "multi-user.target" ];
+            requires = [ "required-service.service" ];
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+              ExecReload = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        unitWithRequirementModified.configuration = {
+          imports = [ unitWithRequirement.configuration ];
+          systemd.services.required-service.serviceConfig.X-Test = "test";
+          systemd.services.test-service.reloadTriggers = [ "test" ];
+        };
+
+        unitWithRequirementModifiedNostart.configuration = {
+          imports = [ unitWithRequirement.configuration ];
+          systemd.services.test-service.unitConfig.RefuseManualStart = true;
+        };
+
+        unitWithTemplate.configuration = {
+          systemd.services."instantiated@".serviceConfig = {
+            Type = "oneshot";
+            RemainAfterExit = true;
+            ExecStart = "${pkgs.coreutils}/bin/true";
+            ExecReload = "${pkgs.coreutils}/bin/true";
+          };
+          systemd.services."instantiated@one" = {
+            wantedBy = [ "multi-user.target" ];
+            overrideStrategy = "asDropin";
+          };
+          systemd.services."instantiated@two" = {
+            wantedBy = [ "multi-user.target" ];
+            overrideStrategy = "asDropin";
+          };
+        };
+
+        unitWithTemplateModified.configuration = {
+          imports = [ unitWithTemplate.configuration ];
+          systemd.services."instantiated@".serviceConfig.X-Test = "test";
+        };
+
+        restart-and-reload-by-activation-script.configuration = {
+          systemd.services = rec {
+            simple-service = {
+              # No wantedBy so we can check if the activation script restart triggers them
+              serviceConfig = {
+                Type = "oneshot";
+                RemainAfterExit = true;
+                ExecStart = "${pkgs.coreutils}/bin/true";
+                ExecReload = "${pkgs.coreutils}/bin/true";
+              };
+            };
+            "templated-simple-service@" = simple-service;
+            "templated-simple-service@instance".overrideStrategy = "asDropin";
+
+            simple-restart-service = simple-service // {
+              stopIfChanged = false;
+            };
+            "templated-simple-restart-service@" = simple-restart-service;
+            "templated-simple-restart-service@instance".overrideStrategy = "asDropin";
+
+            simple-reload-service = simple-service // {
+              reloadIfChanged = true;
+            };
+            "templated-simple-reload-service@" = simple-reload-service;
+            "templated-simple-reload-service@instance".overrideStrategy = "asDropin";
+
+            no-restart-service = simple-service // {
+              restartIfChanged = false;
+            };
+            "templated-no-restart-service@" = no-restart-service;
+            "templated-no-restart-service@instance".overrideStrategy = "asDropin";
+
+            reload-triggers = simple-service // {
+              wantedBy = [ "multi-user.target" ];
+            };
+            "templated-reload-triggers@" = simple-service;
+            "templated-reload-triggers@instance" = {
+              overrideStrategy = "asDropin";
+              wantedBy = [ "multi-user.target" ];
+            };
+
+            reload-triggers-and-restart-by-as = simple-service;
+            "templated-reload-triggers-and-restart-by-as@" = reload-triggers-and-restart-by-as;
+            "templated-reload-triggers-and-restart-by-as@instance".overrideStrategy = "asDropin";
+
+            reload-triggers-and-restart = simple-service // {
+              stopIfChanged = false; # easier to check for this
+              wantedBy = [ "multi-user.target" ];
+            };
+            "templated-reload-triggers-and-restart@" = simple-service;
+            "templated-reload-triggers-and-restart@instance" = {
+              overrideStrategy = "asDropin";
+              stopIfChanged = false; # easier to check for this
+              wantedBy = [ "multi-user.target" ];
+            };
+          };
+
+          system.activationScripts.restart-and-reload-test = {
+            supportsDryActivation = true;
+            deps = [];
+            text = ''
+              if [ "$NIXOS_ACTION" = dry-activate ]; then
+                f=/run/nixos/dry-activation-restart-list
+                g=/run/nixos/dry-activation-reload-list
+              else
+                f=/run/nixos/activation-restart-list
+                g=/run/nixos/activation-reload-list
+              fi
+              cat <<EOF >> "$f"
+              simple-service.service
+              simple-restart-service.service
+              simple-reload-service.service
+              no-restart-service.service
+              reload-triggers-and-restart-by-as.service
+              templated-simple-service@instance.service
+              templated-simple-restart-service@instance.service
+              templated-simple-reload-service@instance.service
+              templated-no-restart-service@instance.service
+              templated-reload-triggers-and-restart-by-as@instance.service
+              EOF
+
+              cat <<EOF >> "$g"
+              reload-triggers.service
+              reload-triggers-and-restart-by-as.service
+              reload-triggers-and-restart.service
+              templated-reload-triggers@instance.service
+              templated-reload-triggers-and-restart-by-as@instance.service
+              templated-reload-triggers-and-restart@instance.service
+              EOF
+            '';
+          };
+        };
+
+        restart-and-reload-by-activation-script-modified.configuration = {
+          imports = [ restart-and-reload-by-activation-script.configuration ];
+          systemd.services.reload-triggers-and-restart.serviceConfig.X-Modified = "test";
+          systemd.services."templated-reload-triggers-and-restart@instance" = {
+            overrideStrategy = "asDropin";
+            serviceConfig.X-Modified = "test";
+          };
+        };
+
+        simple-socket.configuration = {
+          systemd.services.socket-activated = {
+            description = "A socket-activated service";
+            stopIfChanged = lib.mkDefault false;
+            serviceConfig = {
+              ExecStart = socketTest;
+              ExecReload = "${pkgs.coreutils}/bin/true";
+            };
+          };
+          systemd.sockets.socket-activated = {
+            wantedBy = [ "sockets.target" ];
+            listenStreams = [ "/run/test.sock" ];
+            socketConfig.SocketMode = lib.mkDefault "0777";
+          };
+        };
+
+        simple-socket-service-modified.configuration = {
+          imports = [ simple-socket.configuration ];
+          systemd.services.socket-activated.serviceConfig.X-Test = "test";
+        };
+
+        simple-socket-stop-if-changed.configuration = {
+          imports = [ simple-socket.configuration ];
+          systemd.services.socket-activated.stopIfChanged = true;
+        };
+
+        simple-socket-stop-if-changed-and-reloadtrigger.configuration = {
+          imports = [ simple-socket.configuration ];
+          systemd.services.socket-activated = {
+            stopIfChanged = true;
+            reloadTriggers = [ "test" ];
+          };
+        };
+
+        mount.configuration = {
+          systemd.mounts = [
+            {
+              description = "Testmount";
+              what = "tmpfs";
+              type = "tmpfs";
+              where = "/testmount";
+              options = "size=1M";
+              wantedBy = [ "local-fs.target" ];
+            }
+          ];
+        };
+
+        mountOptionsModified.configuration = {
+          systemd.mounts = [
+            {
+              description = "Testmount";
+              what = "tmpfs";
+              type = "tmpfs";
+              where = "/testmount";
+              options = "size=10M";
+              wantedBy = [ "local-fs.target" ];
+            }
+          ];
+        };
+
+        mountModified.configuration = {
+          systemd.mounts = [
+            {
+              description = "Testmount";
+              what = "ramfs";
+              type = "ramfs";
+              where = "/testmount";
+              options = "size=10M";
+              wantedBy = [ "local-fs.target" ];
+            }
+          ];
+        };
+
+        timer.configuration = {
+          systemd.timers.test-timer = {
+            wantedBy = [ "timers.target" ];
+            timerConfig.OnCalendar = "@1395716396"; # chosen by fair dice roll
+          };
+          systemd.services.test-timer = {
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = "${pkgs.coreutils}/bin/true";
+            };
+          };
+        };
+
+        timerModified.configuration = {
+          imports = [ timer.configuration ];
+          systemd.timers.test-timer.timerConfig.OnCalendar = lib.mkForce "Fri 2012-11-23 16:00:00";
+        };
+
+        hybridSleepModified.configuration = {
+          systemd.targets.hybrid-sleep.unitConfig.X-Test = true;
+        };
+
+        target.configuration = {
+          systemd.targets.test-target.wantedBy = [ "multi-user.target" ];
+          # We use this service to figure out whether the target was modified.
+          # This is the only way because targets are filtered and therefore not
+          # printed when they are started/stopped.
+          systemd.services.test-service = {
+            bindsTo = [ "test-target.target" ];
+            serviceConfig.ExecStart = "${pkgs.coreutils}/bin/sleep infinity";
+          };
+        };
+
+        targetModified.configuration = {
+          imports = [ target.configuration ];
+          systemd.targets.test-target.unitConfig.X-Test = true;
+        };
+
+        targetModifiedStopOnReconfig.configuration = {
+          imports = [ target.configuration ];
+          systemd.targets.test-target.unitConfig.X-StopOnReconfiguration = true;
+        };
+
+        path.configuration = {
+          systemd.paths.test-watch = {
+            wantedBy = [ "paths.target" ];
+            pathConfig.PathExists = "/testpath";
+          };
+          systemd.services.test-watch = {
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/touch /testpath-modified";
+            };
+          };
+        };
+
+        pathModified.configuration = {
+          imports = [ path.configuration ];
+          systemd.paths.test-watch.pathConfig.PathExists = lib.mkForce "/testpath2";
+        };
+
+        slice.configuration = {
+          systemd.slices.testslice.sliceConfig.MemoryMax = "1"; # don't allow memory allocation
+          systemd.services.testservice = {
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStart = "${pkgs.coreutils}/bin/true";
+              Slice = "testslice.slice";
+            };
+          };
+        };
+
+        sliceModified.configuration = {
+          imports = [ slice.configuration ];
+          systemd.slices.testslice.sliceConfig.MemoryMax = lib.mkForce null;
+        };
+      };
+    };
+
+    other = {
+      users.mutableUsers = true;
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    originalSystem = nodes.machine.system.build.toplevel;
+    otherSystem = nodes.other.system.build.toplevel;
+    machine = nodes.machine.system.build.toplevel;
+
+    # Ensures failures pass through using pipefail, otherwise failing to
+    # switch-to-configuration is hidden by the success of `tee`.
+    stderrRunner = pkgs.writeScript "stderr-runner" ''
+      #! ${pkgs.runtimeShell}
+      set -e
+      set -o pipefail
+      exec env -i "$@" | tee /dev/stderr
+    '';
+
+    # Returns a comma separated representation of the given list in sorted
+    # order, that matches the output format of switch-to-configuration.pl
+    sortedUnits = xs: lib.concatStringsSep ", " (builtins.sort builtins.lessThan xs);
+  in /* python */ ''
+    def switch_to_specialisation(system, name, action="test", fail=False):
+        if name == "":
+            switcher = f"{system}/bin/switch-to-configuration"
+        else:
+            switcher = f"{system}/specialisation/{name}/bin/switch-to-configuration"
+        return run_switch(switcher, action, fail)
+
+    # like above but stc = switcher
+    def run_switch(switcher, action="test", fail=False):
+        out = machine.fail(f"{switcher} {action} 2>&1") if fail \
+            else machine.succeed(f"{switcher} {action} 2>&1")
+        assert_lacks(out, "switch-to-configuration line")  # Perl warnings
+        return out
+
+    def assert_contains(haystack, needle):
+        if needle not in haystack:
+            print("The haystack that will cause the following exception is:")
+            print("---")
+            print(haystack)
+            print("---")
+            raise Exception(f"Expected string '{needle}' was not found")
+
+    def assert_lacks(haystack, needle):
+        if needle in haystack:
+            print("The haystack that will cause the following exception is:")
+            print("---")
+            print(haystack, end="")
+            print("---")
+            raise Exception(f"Unexpected string '{needle}' was found")
+
+
+    machine.wait_for_unit("multi-user.target")
+
+    machine.succeed(
+        "${stderrRunner} ${originalSystem}/bin/switch-to-configuration test"
+    )
+    # This tests whether the /etc/os-release parser works which is a fallback
+    # when /etc/NIXOS is missing. If the parser does not work, switch-to-configuration
+    # would fail.
+    machine.succeed("rm /etc/NIXOS")
+    machine.succeed(
+        "${stderrRunner} ${otherSystem}/bin/switch-to-configuration test"
+    )
+
+
+    with subtest("actions"):
+        # boot action
+        machine.fail("test -f /tmp/bootloader-installed")
+        out = switch_to_specialisation("${machine}", "simpleService", action="boot")
+        assert_contains(out, "installing dummy bootloader")
+        assert_lacks(out, "activating the configuration...")  # good indicator of a system activation
+        machine.succeed("test -f /tmp/bootloader-installed")
+        machine.succeed("rm /tmp/bootloader-installed")
+
+        # switch action
+        machine.fail("test -f /tmp/bootloader-installed")
+        out = switch_to_specialisation("${machine}", "", action="switch")
+        assert_contains(out, "installing dummy bootloader")
+        assert_contains(out, "activating the configuration...")  # good indicator of a system activation
+        machine.succeed("test -f /tmp/bootloader-installed")
+
+        # test and dry-activate actions are tested further down below
+
+        # invalid action fails the script
+        switch_to_specialisation("${machine}", "", action="broken-action", fail=True)
+        # no action fails the script
+        assert "Usage:" in machine.fail("${machine}/bin/switch-to-configuration 2>&1")
+
+    with subtest("init interface version"):
+        # Do not try to switch to an invalid init interface version
+        assert "incompatible" in switch_to_specialisation("${machine}", "brokenInitInterface", fail=True)
+
+    with subtest("systemd restarts"):
+        # systemd is restarted when its system.conf changes
+        out = switch_to_specialisation("${machine}", "modifiedSystemConf")
+        assert_contains(out, "restarting systemd...")
+
+    with subtest("continuing from an aborted switch"):
+        # An aborted switch will write into a file what it tried to start
+        # and a second switch should continue from this
+        machine.succeed("echo dbus.service > /run/nixos/start-list")
+        out = switch_to_specialisation("${machine}", "modifiedSystemConf")
+        assert_contains(out, "starting the following units: dbus.service\n")
+
+    with subtest("fstab mounts"):
+        switch_to_specialisation("${machine}", "")
+        # add a mountpoint
+        out = switch_to_specialisation("${machine}", "addedMount")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: test.mount\n")
+        # modify the mountpoint's options
+        out = switch_to_specialisation("${machine}", "addedMountOptsModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: test.mount\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # modify the device
+        out = switch_to_specialisation("${machine}", "addedMountDevModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.mount\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # modify both
+        out = switch_to_specialisation("${machine}", "addedMount")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.mount\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # remove the mount
+        out = switch_to_specialisation("${machine}", "")
+        assert_contains(out, "stopping the following units: test.mount\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # change something about the / mount
+        out = switch_to_specialisation("${machine}", "storeMountModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_contains(out, "NOT restarting the following changed units: -.mount")
+        assert_contains(out, "reloading the following units: dbus.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+    with subtest("swaps"):
+        switch_to_specialisation("${machine}", "")
+        # add a swap
+        out = switch_to_specialisation("${machine}", "swap")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: swapfile.swap")
+        # remove it
+        out = switch_to_specialisation("${machine}", "")
+        assert_contains(out, "stopping swap device: /swapfile")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+    with subtest("services"):
+        switch_to_specialisation("${machine}", "")
+        # Nothing happens when nothing is changed
+        out = switch_to_specialisation("${machine}", "")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Start a simple service
+        out = switch_to_specialisation("${machine}", "simpleService")
+        assert_lacks(out, "installing dummy bootloader")  # test does not install a bootloader
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")  # huh
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: test.service\n")
+
+        # Not changing anything doesn't do anything
+        out = switch_to_specialisation("${machine}", "simpleService")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Only changing the description does nothing
+        out = switch_to_specialisation("${machine}", "simpleServiceDifferentDescription")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Restart the simple service
+        out = switch_to_specialisation("${machine}", "simpleServiceModified")
+        assert_contains(out, "stopping the following units: test.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: test.service\n")
+        assert_lacks(out, "the following new units were started:")
+
+        # Restart the service with stopIfChanged=false
+        out = switch_to_specialisation("${machine}", "simpleServiceNostop")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Reload the service with reloadIfChanged=true
+        out = switch_to_specialisation("${machine}", "simpleServiceReload")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: test.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Nothing happens when restartIfChanged=false
+        out = switch_to_specialisation("${machine}", "simpleServiceNorestart")
+        assert_lacks(out, "stopping the following units:")
+        assert_contains(out, "NOT restarting the following changed units: test.service\n")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Dry mode shows different messages
+        out = switch_to_specialisation("${machine}", "simpleService", action="dry-activate")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_contains(out, "would start the following units: test.service\n")
+
+        out = switch_to_specialisation("${machine}", "", action="test")
+
+        # Ensure the service can be started when the activation script isn't in toplevel
+        # This is a lot like "Start a simple service", except activation-only deps could be gc-ed
+        out = run_switch("${nodes.machine.specialisation.simpleServiceSeparateActivationScript.configuration.system.build.separateActivationScript}/bin/switch-to-configuration");
+        assert_lacks(out, "installing dummy bootloader")  # test does not install a bootloader
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")  # huh
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: test.service\n")
+        machine.succeed("! test -e /run/current-system/activate")
+        machine.succeed("! test -e /run/current-system/dry-activate")
+        machine.succeed("! test -e /run/current-system/bin/switch-to-configuration")
+
+        # Ensure \ works in unit names
+        out = switch_to_specialisation("${machine}", "unitWithBackslash")
+        assert_contains(out, "stopping the following units: test.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: escaped\\x2ddash.service\n")
+
+        out = switch_to_specialisation("${machine}", "unitWithBackslashModified")
+        assert_contains(out, "stopping the following units: escaped\\x2ddash.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: escaped\\x2ddash.service\n")
+        assert_lacks(out, "the following new units were started:")
+
+        # Ensure units can start with a dash
+        out = switch_to_specialisation("${machine}", "unitStartingWithDash")
+        assert_contains(out, "stopping the following units: escaped\\x2ddash.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: -.service\n")
+
+        # The regression only occurs when reloading units
+        out = switch_to_specialisation("${machine}", "unitStartingWithDashModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: -.service")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Ensure units that require changed units are properly reloaded
+        out = switch_to_specialisation("${machine}", "unitWithRequirement")
+        assert_contains(out, "stopping the following units: -.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: required-service.service, test-service.service\n")
+
+        out = switch_to_specialisation("${machine}", "unitWithRequirementModified")
+        assert_contains(out, "stopping the following units: required-service.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: required-service.service, test-service.service\n")
+        assert_lacks(out, "the following new units were started:")
+
+        # Unless the unit asks to be not restarted
+        out = switch_to_specialisation("${machine}", "unitWithRequirementModifiedNostart")
+        assert_contains(out, "stopping the following units: required-service.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: required-service.service\n")
+        assert_lacks(out, "the following new units were started:")
+
+        # Ensure templated units are restarted when the base unit changes
+        switch_to_specialisation("${machine}", "unitWithTemplate")
+        out = switch_to_specialisation("${machine}", "unitWithTemplateModified")
+        assert_contains(out, "stopping the following units: instantiated@one.service, instantiated@two.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: instantiated@one.service, instantiated@two.service\n")
+        assert_lacks(out, "the following new units were started:")
+
+    with subtest("failing units"):
+        # Let the simple service fail
+        switch_to_specialisation("${machine}", "simpleServiceModified")
+        out = switch_to_specialisation("${machine}", "simpleServiceFailing", fail=True)
+        assert_contains(out, "stopping the following units: test.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: test.service\n")
+        assert_lacks(out, "the following new units were started:")
+        assert_contains(out, "warning: the following units failed: test.service\n")
+        assert_contains(out, "Main PID:")  # output of systemctl
+
+        # A unit that gets into autorestart without failing is not treated as failed
+        out = switch_to_specialisation("${machine}", "autorestartService")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: autorestart.service\n")
+        machine.systemctl('stop autorestart.service')  # cancel the 20y timer
+
+        # Switching to the same system should do nothing (especially not treat the unit as failed)
+        out = switch_to_specialisation("${machine}", "autorestartService")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: autorestart.service\n")
+        machine.systemctl('stop autorestart.service')  # cancel the 20y timer
+
+        # If systemd thinks the unit has failed and is in autorestart, we should show it as failed
+        out = switch_to_specialisation("${machine}", "autorestartServiceFailing", fail=True)
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        assert_contains(out, "warning: the following units failed: autorestart.service\n")
+        assert_contains(out, "Main PID:")  # output of systemctl
+
+    with subtest("unit file parser"):
+        # Switch to a well-known state
+        switch_to_specialisation("${machine}", "simpleServiceNostop")
+
+        # Add a section
+        out = switch_to_specialisation("${machine}", "simpleServiceWithExtraSection")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Rename it
+        out = switch_to_specialisation("${machine}", "simpleServiceWithExtraSectionOtherName")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Remove it
+        out = switch_to_specialisation("${machine}", "simpleServiceNostop")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # [Install] section is ignored
+        out = switch_to_specialisation("${machine}", "simpleServiceWithInstallSection")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Add a key
+        out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKey")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Change its value
+        out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKeyOtherValue")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Rename it
+        out = switch_to_specialisation("${machine}", "simpleServiceWithExtraKeyOtherName")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Remove it
+        out = switch_to_specialisation("${machine}", "simpleServiceNostop")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Add a reload trigger
+        out = switch_to_specialisation("${machine}", "simpleServiceReloadTrigger")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: test.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Modify the reload trigger
+        out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: test.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Modify the reload trigger and something else
+        out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModifiedAndSomethingElse")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Remove the reload trigger
+        out = switch_to_specialisation("${machine}", "simpleServiceReloadTriggerModifiedSomethingElse")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+    with subtest("restart and reload by activation script"):
+        switch_to_specialisation("${machine}", "simpleServiceNorestart")
+        out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script")
+        assert_contains(out, "stopping the following units: test.service\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "restarting the following units:")
+        assert_contains(out, "\nstarting the following units: ${sortedUnits [
+          "no-restart-service.service"
+          "reload-triggers-and-restart-by-as.service"
+          "simple-reload-service.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "templated-no-restart-service@instance.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-simple-reload-service@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
+        assert_contains(out, "the following new units were started: ${sortedUnits [
+          "no-restart-service.service"
+          "reload-triggers-and-restart-by-as.service"
+          "reload-triggers-and-restart.service"
+          "reload-triggers.service"
+          "simple-reload-service.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "system-templated\\\\x2dno\\\\x2drestart\\\\x2dservice.slice"
+          "system-templated\\\\x2dreload\\\\x2dtriggers.slice"
+          "system-templated\\\\x2dreload\\\\x2dtriggers\\\\x2dand\\\\x2drestart.slice"
+          "system-templated\\\\x2dreload\\\\x2dtriggers\\\\x2dand\\\\x2drestart\\\\x2dby\\\\x2das.slice"
+          "system-templated\\\\x2dsimple\\\\x2dreload\\\\x2dservice.slice"
+          "system-templated\\\\x2dsimple\\\\x2drestart\\\\x2dservice.slice"
+          "system-templated\\\\x2dsimple\\\\x2dservice.slice"
+          "templated-no-restart-service@instance.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-reload-triggers-and-restart@instance.service"
+          "templated-reload-triggers@instance.service"
+          "templated-simple-reload-service@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
+        # Switch to the same system where the example services get restarted
+        # and reloaded by the activation script
+        out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: ${sortedUnits [
+          "reload-triggers-and-restart.service"
+          "reload-triggers.service"
+          "simple-reload-service.service"
+          "templated-reload-triggers-and-restart@instance.service"
+          "templated-reload-triggers@instance.service"
+          "templated-simple-reload-service@instance.service"
+        ]}\n")
+        assert_contains(out, "restarting the following units: ${sortedUnits [
+          "reload-triggers-and-restart-by-as.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # Switch to the same system and see if the service gets restarted when it's modified
+        # while the fact that it's supposed to be reloaded by the activation script is ignored.
+        out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script-modified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: ${sortedUnits [
+          "reload-triggers.service"
+          "simple-reload-service.service"
+          "templated-reload-triggers@instance.service"
+          "templated-simple-reload-service@instance.service"
+        ]}\n")
+        assert_contains(out, "restarting the following units: ${sortedUnits [
+          "reload-triggers-and-restart-by-as.service"
+          "reload-triggers-and-restart.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-reload-triggers-and-restart@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # The same, but in dry mode
+        out = switch_to_specialisation("${machine}", "restart-and-reload-by-activation-script", action="dry-activate")
+        assert_lacks(out, "would stop the following units:")
+        assert_lacks(out, "would NOT stop the following changed units:")
+        assert_contains(out, "would reload the following units: ${sortedUnits [
+          "reload-triggers.service"
+          "simple-reload-service.service"
+          "templated-reload-triggers@instance.service"
+          "templated-simple-reload-service@instance.service"
+        ]}\n")
+        assert_contains(out, "would restart the following units: ${sortedUnits [
+          "reload-triggers-and-restart-by-as.service"
+          "reload-triggers-and-restart.service"
+          "simple-restart-service.service"
+          "simple-service.service"
+          "templated-reload-triggers-and-restart-by-as@instance.service"
+          "templated-reload-triggers-and-restart@instance.service"
+          "templated-simple-restart-service@instance.service"
+          "templated-simple-service@instance.service"
+        ]}\n")
+        assert_lacks(out, "\nwould start the following units:")
+
+    with subtest("socket-activated services"):
+        # Socket-activated services don't get started, just the socket
+        machine.fail("[ -S /run/test.sock ]")
+        out = switch_to_specialisation("${machine}", "simple-socket")
+        # assert_lacks(out, "stopping the following units:") not relevant
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: socket-activated.socket\n")
+        machine.succeed("[ -S /run/test.sock ]")
+
+        # Changing a non-activated service does nothing
+        out = switch_to_specialisation("${machine}", "simple-socket-service-modified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("[ -S /run/test.sock ]")
+        # The unit is properly activated when the socket is accessed
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated")  # idk how that would happen tbh
+
+        # Changing an activated service with stopIfChanged=false restarts the service
+        out = switch_to_specialisation("${machine}", "simple-socket")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: socket-activated.service\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("[ -S /run/test.sock ]")
+        # Socket-activation of the unit still works
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated after the service was restarted")
+
+        # Changing an activated service with stopIfChanged=true stops the service and
+        # socket and starts the socket
+        out = switch_to_specialisation("${machine}", "simple-socket-stop-if-changed")
+        assert_contains(out, "stopping the following units: socket-activated.service, socket-activated.socket\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_contains(out, "\nstarting the following units: socket-activated.socket\n")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("[ -S /run/test.sock ]")
+        # Socket-activation of the unit still works
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated after the service was restarted")
+
+        # Changing a reload trigger of a socket-activated unit only reloads it
+        out = switch_to_specialisation("${machine}", "simple-socket-stop-if-changed-and-reloadtrigger")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: socket-activated.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units: socket-activated.socket")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("[ -S /run/test.sock ]")
+        # Socket-activation of the unit still works
+        if machine.succeed("socat - UNIX-CONNECT:/run/test.sock") != "hello":
+            raise Exception("Socket was not properly activated after the service was restarted")
+
+    with subtest("mounts"):
+        switch_to_specialisation("${machine}", "mount")
+        out = machine.succeed("mount | grep 'on /testmount'")
+        assert_contains(out, "size=1024k")
+        # Changing options reloads the unit
+        out = switch_to_specialisation("${machine}", "mountOptionsModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: testmount.mount\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # It changed
+        out = machine.succeed("mount | grep 'on /testmount'")
+        assert_contains(out, "size=10240k")
+        # Changing anything but `Options=` restarts the unit
+        out = switch_to_specialisation("${machine}", "mountModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: testmount.mount\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # It changed
+        out = machine.succeed("mount | grep 'on /testmount'")
+        assert_contains(out, "ramfs")
+
+    with subtest("timers"):
+        switch_to_specialisation("${machine}", "timer")
+        out = machine.succeed("systemctl show test-timer.timer")
+        assert_contains(out, "OnCalendar=2014-03-25 02:59:56 UTC")
+        out = switch_to_specialisation("${machine}", "timerModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test-timer.timer\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # It changed
+        out = machine.succeed("systemctl show test-timer.timer")
+        assert_contains(out, "OnCalendar=Fri 2012-11-23 16:00:00")
+
+    with subtest("targets"):
+        # Modifying some special targets like hybrid-sleep.target does nothing
+        out = switch_to_specialisation("${machine}", "hybridSleepModified")
+        assert_contains(out, "stopping the following units: test-timer.timer\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+        # Adding a new target starts it
+        out = switch_to_specialisation("${machine}", "target")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: test-target.target\n")
+
+        # Changing a target doesn't print anything because the unit is filtered
+        machine.systemctl("start test-service.service")
+        out = switch_to_specialisation("${machine}", "targetModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("systemctl is-active test-service.service")  # target was not restarted
+
+        # With X-StopOnReconfiguration, the target gets stopped and started
+        out = switch_to_specialisation("${machine}", "targetModifiedStopOnReconfig")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        machine.fail("systemctl is-active test-service.servce")  # target was restarted
+
+        # Remove the target by switching to the old specialisation
+        out = switch_to_specialisation("${machine}", "timerModified")
+        assert_contains(out, "stopping the following units: test-target.target\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: test-timer.timer\n")
+
+    with subtest("paths"):
+        out = switch_to_specialisation("${machine}", "path")
+        assert_contains(out, "stopping the following units: test-timer.timer\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: test-watch.path\n")
+        machine.fail("test -f /testpath-modified")
+
+        # touch the file, unit should be triggered
+        machine.succeed("touch /testpath")
+        machine.wait_until_succeeds("test -f /testpath-modified")
+        machine.succeed("rm /testpath /testpath-modified")
+        machine.systemctl("stop test-watch.service")
+        switch_to_specialisation("${machine}", "pathModified")
+        machine.succeed("touch /testpath")
+        machine.fail("test -f /testpath-modified")
+        machine.succeed("touch /testpath2")
+        machine.wait_until_succeeds("test -f /testpath-modified")
+
+    # This test ensures that changes to slice configuration get applied.
+    # We test this by having a slice that allows no memory allocation at
+    # all and starting a service within it. If the service crashes, the slice
+    # is applied and if we modify the slice to allow memory allocation, the
+    # service should successfully start.
+    with subtest("slices"):
+        machine.succeed("echo 0 > /proc/sys/vm/panic_on_oom")  # allow OOMing
+        out = switch_to_specialisation("${machine}", "slice")
+        # assert_lacks(out, "stopping the following units:") not relevant
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        machine.fail("systemctl start testservice.service")
+
+        out = switch_to_specialisation("${machine}", "sliceModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        machine.succeed("systemctl start testservice.service")
+        machine.succeed("echo 1 > /proc/sys/vm/panic_on_oom")  # disallow OOMing
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sympa.nix b/nixpkgs/nixos/tests/sympa.nix
new file mode 100644
index 000000000000..80daa4134f75
--- /dev/null
+++ b/nixpkgs/nixos/tests/sympa.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "sympa";
+  meta.maintainers = with lib.maintainers; [ mmilata ];
+
+  nodes.machine =
+    { ... }:
+    {
+
+      services.sympa = {
+        enable = true;
+        domains = {
+          "lists.example.org" = {
+            webHost = "localhost";
+          };
+        };
+        listMasters = [ "bob@example.org" ];
+        web.enable = true;
+        web.https = false;
+        database = {
+          type = "PostgreSQL";
+          createLocally = true;
+        };
+      };
+    };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("sympa.service")
+    machine.wait_for_unit("wwsympa.service")
+    assert "Mailing lists service" in machine.succeed(
+        "curl --fail --insecure -L http://localhost/"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/syncthing-init.nix b/nixpkgs/nixos/tests/syncthing-init.nix
new file mode 100644
index 000000000000..97fcf2ad28d1
--- /dev/null
+++ b/nixpkgs/nixos/tests/syncthing-init.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: let
+
+  testId = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+
+in {
+  name = "syncthing-init";
+  meta.maintainers = with pkgs.lib.maintainers; [ lassulus ];
+
+  nodes.machine = {
+    services.syncthing = {
+      enable = true;
+      settings.devices.testDevice = {
+        id = testId;
+      };
+      settings.folders.testFolder = {
+        path = "/tmp/test";
+        devices = [ "testDevice" ];
+      };
+      settings.gui.user = "guiUser";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("syncthing-init.service")
+    config = machine.succeed("cat /var/lib/syncthing/.config/syncthing/config.xml")
+
+    assert "testFolder" in config
+    assert "${testId}" in config
+    assert "guiUser" in config
+  '';
+})
diff --git a/nixpkgs/nixos/tests/syncthing-many-devices.nix b/nixpkgs/nixos/tests/syncthing-many-devices.nix
new file mode 100644
index 000000000000..2251bf077453
--- /dev/null
+++ b/nixpkgs/nixos/tests/syncthing-many-devices.nix
@@ -0,0 +1,203 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+# This nixosTest is supposed to check the following:
+#
+# - Whether syncthing's API handles multiple requests for many devices, see
+#   https://github.com/NixOS/nixpkgs/issues/260262
+#
+# - Whether syncthing-init.service generated bash script removes devices and
+#   folders that are not present in the user's configuration, which is partly
+#   injected into the script. See also:
+#   https://github.com/NixOS/nixpkgs/issues/259256
+#
+
+let
+  # Just a long path not to copy paste
+  configPath = "/var/lib/syncthing/.config/syncthing/config.xml";
+
+  # We will iterate this and more attribute sets defined here, later in the
+  # testScript. Start with this, and distinguish these settings from other
+  # settings, as we check these differently with xmllint, due to the ID.
+  settingsWithId = {
+    devices = {
+      # All of the device IDs used here were generated by the following command:
+      #
+      #    (${pkgs.syncthing}/bin/syncthing generate --home /tmp/foo\
+      #       | grep ID: | sed 's/.*ID: *//') && rm -rf /tmp/foo
+      #
+      # See also discussion at:
+      # https://forum.syncthing.net/t/how-to-generate-dummy-device-ids/20927/8
+      test_device1.id  = "IVTZ5XF-EF3GKFT-GS4AZLG-IT6H2ZP-6WK75SF-AFXQXJJ-BNRZ4N6-XPDKVAU";
+      test_device2.id  = "5C35H56-Z2GFF4F-F3IVD4B-GJYVWIE-SMDBJZN-GI66KWP-52JIQGN-4AVLYAM";
+      test_device3.id  = "XKLSKHE-BZOHV7B-WQZACEF-GTH36NP-6JSBB6L-RXS3M7C-EEVWO2L-C5B4OAJ";
+      test_device4.id  = "APN5Q7J-35GZETO-5KCLF35-ZA7KBWK-HGWPBNG-FERF24R-UTLGMEX-4VJ6PQX";
+      test_device5.id  = "D4YXQEE-5MK6LIK-BRU5QWM-ZRXJCK2-N3RQBJE-23JKTQQ-LYGDPHF-RFPZIQX";
+      test_device6.id  = "TKMCH64-T44VSLI-6FN2YLF-URBZOBR-ATO4DYX-GEDRIII-CSMRQAI-UAQMDQG";
+      test_device7.id  = "472EEBG-Q4PZCD4-4CX6PGF-XS3FSQ2-UFXBZVB-PGNXWLX-7FKBLER-NJ3EMAR";
+      test_device8.id  = "HW6KUMK-WTBG24L-2HZQXLO-TGJSG2M-2JG3FHX-5OGYRUJ-T6L5NN7-L364QAZ";
+      test_device9.id  = "YAE24AP-7LSVY4T-J74ZSEM-A2IK6RB-FGA35TP-AG4CSLU-ED4UYYY-2J2TDQU";
+      test_device10.id = "277XFSB-OFMQOBI-3XGNGUE-Y7FWRV3-QQDADIY-QIIPQ26-EOGTYKW-JP2EXAI";
+      test_device11.id = "2WWXVTN-Q3QWAAY-XFORMRM-2FDI5XZ-OGN33BD-XOLL42R-DHLT2ML-QYXDQAU";
+    };
+    # Generates a few folders with IDs and paths as written...
+    folders = lib.pipe 6 [
+      (builtins.genList (x: {
+        name = "/var/lib/syncthing/test_folder${builtins.toString x}";
+        value = {
+          id = "DontDeleteMe${builtins.toString x}";
+        };
+      }))
+      builtins.listToAttrs
+    ];
+  };
+  # Non default options that we check later if were applied
+  settingsWithoutId = {
+    options = {
+      autoUpgradeIntervalH = 0;
+      urAccepted = -1;
+    };
+    gui = {
+      theme = "dark";
+    };
+  };
+  # Used later when checking whether settings were set in config.xml:
+  checkSettingWithId = { t # t for type
+  , id
+  , not ? false
+  }: ''
+    print("Searching for a ${t} with id ${id}")
+    configVal_${t} = machine.succeed(
+        "${pkgs.libxml2}/bin/xmllint "
+        "--xpath 'string(//${t}[@id=\"${id}\"]/@id)' ${configPath}"
+    )
+    print("${t}.id = {}".format(configVal_${t}))
+    assert "${id}" ${if not then "not" else ""} in configVal_${t}
+  '';
+  # Same as checkSettingWithId, but for 'options' and 'gui'
+  checkSettingWithoutId = { t # t for type
+  , n # n for name
+  , v # v for value
+  , not ? false
+  }: ''
+    print("checking whether setting ${t}.${n} is set to ${v}")
+    configVal_${t}_${n} = machine.succeed(
+        "${pkgs.libxml2}/bin/xmllint "
+        "--xpath 'string(/configuration/${t}/${n})' ${configPath}"
+    )
+    print("${t}.${n} = {}".format(configVal_${t}_${n}))
+    assert "${v}" ${if not then "not" else ""} in configVal_${t}_${n}
+  '';
+  # Removes duplication a bit to define this function for the IDs to delete -
+  # we check whether they were added after our script ran, and before the
+  # systemd unit's bash script ran, and afterwards - whether the systemd unit
+  # worked.
+  checkSettingsToDelete = {
+    not
+  }: lib.pipe IDsToDelete [
+    (lib.mapAttrsToList (t: id:
+      checkSettingWithId {
+        inherit t id;
+        inherit not;
+      }
+    ))
+    lib.concatStrings
+  ];
+  # These IDs are added to syncthing using the API, similarly to how the
+  # generated systemd unit's bash script does it. Only we add it and expect the
+  # systemd unit bash script to remove them when executed.
+  IDsToDelete = {
+    # Also created using the syncthing generate command above
+    device = "LZ2CTHT-3W2M7BC-CMKDFZL-DLUQJFS-WJR73PA-NZGODWG-DZBHCHI-OXTQXAK";
+    # Intentionally this is a substring of the IDs of the 'test_folder's, as
+    # explained in: https://github.com/NixOS/nixpkgs/issues/259256
+    folder = "DeleteMe";
+  };
+  addDeviceToDeleteScript = pkgs.writers.writeBash "syncthing-add-device-to-delete.sh" ''
+    set -euo pipefail
+
+    export RUNTIME_DIRECTORY=/tmp
+
+    curl() {
+        # get the api key by parsing the config.xml
+        while
+            ! ${pkgs.libxml2}/bin/xmllint \
+                --xpath 'string(configuration/gui/apikey)' \
+                ${configPath} \
+                >"$RUNTIME_DIRECTORY/api_key"
+        do sleep 1; done
+
+        (printf "X-API-Key: "; cat "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
+
+        ${pkgs.curl}/bin/curl -sSLk -H "@$RUNTIME_DIRECTORY/headers" \
+            --retry 1000 --retry-delay 1 --retry-all-errors \
+            "$@"
+    }
+    curl -d ${lib.escapeShellArg (builtins.toJSON { deviceID = IDsToDelete.device;})} \
+        -X POST 127.0.0.1:8384/rest/config/devices
+    curl -d ${lib.escapeShellArg (builtins.toJSON { id = IDsToDelete.folder;})} \
+        -X POST 127.0.0.1:8384/rest/config/folders
+  '';
+in {
+  name = "syncthing-init";
+  meta.maintainers = with lib.maintainers; [ doronbehar ];
+
+  nodes.machine = {
+    services.syncthing = {
+      enable = true;
+      overrideDevices = true;
+      overrideFolders = true;
+      settings = settingsWithoutId // settingsWithId;
+    };
+  };
+  testScript = ''
+    machine.wait_for_unit("syncthing-init.service")
+  '' + (lib.pipe settingsWithId [
+    # Check that folders and devices were added properly and that all IDs exist
+    (lib.mapAttrsRecursive (path: id:
+      checkSettingWithId {
+        # plural -> solitary
+        t = (lib.removeSuffix "s" (builtins.elemAt path 0));
+        inherit id;
+      }
+    ))
+    # Get all the values we applied the above function upon
+    (lib.collect builtins.isString)
+    lib.concatStrings
+  ]) + (lib.pipe settingsWithoutId [
+    # Check that all other syncthing.settings were added properly with correct
+    # values
+    (lib.mapAttrsRecursive (path: value:
+      checkSettingWithoutId {
+        t = (builtins.elemAt path 0);
+        n = (builtins.elemAt path 1);
+        v = (builtins.toString value);
+      }
+    ))
+    # Get all the values we applied the above function upon
+    (lib.collect builtins.isString)
+    lib.concatStrings
+  ]) + ''
+    # Run the script on the machine
+    machine.succeed("${addDeviceToDeleteScript}")
+  '' + (checkSettingsToDelete {
+    not = false;
+  }) + ''
+    # Useful for debugging later
+    machine.copy_from_vm("${configPath}", "before")
+
+    machine.systemctl("restart syncthing-init.service")
+    machine.wait_for_unit("syncthing-init.service")
+  '' + (checkSettingsToDelete {
+    not = true;
+  }) + ''
+    # Useful for debugging later
+    machine.copy_from_vm("${configPath}", "after")
+
+    # Copy the systemd unit's bash script, to inspect it for debugging.
+    mergeScript = machine.succeed(
+        "systemctl cat syncthing-init.service | "
+        "${pkgs.initool}/bin/initool g - Service ExecStart --value-only"
+    ).strip() # strip from new lines
+    machine.copy_from_vm(mergeScript, "")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/syncthing-no-settings.nix b/nixpkgs/nixos/tests/syncthing-no-settings.nix
new file mode 100644
index 000000000000..fee122b5e35c
--- /dev/null
+++ b/nixpkgs/nixos/tests/syncthing-no-settings.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "syncthing";
+  meta.maintainers = with pkgs.lib.maintainers; [ chkno ];
+
+  nodes = {
+    a = {
+      environment.systemPackages = with pkgs; [ curl libxml2 syncthing ];
+      services.syncthing = {
+        enable = true;
+      };
+    };
+  };
+  # Test that indeed a syncthing-init.service systemd service is not created.
+  #
+  testScript = /* python */ ''
+    a.succeed("systemctl list-unit-files | awk '$1 == \"syncthing-init.service\" {exit 1;}'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/syncthing-relay.nix b/nixpkgs/nixos/tests/syncthing-relay.nix
new file mode 100644
index 000000000000..3d70b1eda7b2
--- /dev/null
+++ b/nixpkgs/nixos/tests/syncthing-relay.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "syncthing-relay";
+  meta.maintainers = with pkgs.lib.maintainers; [ delroth ];
+
+  nodes.machine = {
+    environment.systemPackages = [ pkgs.jq ];
+    services.syncthing.relay = {
+      enable = true;
+      providedBy = "nixos-test";
+      pools = [];  # Don't connect to any pool while testing.
+      port = 12345;
+      statusPort = 12346;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("syncthing-relay.service")
+    machine.wait_for_open_port(12345)
+    machine.wait_for_open_port(12346)
+
+    out = machine.succeed(
+        "curl -sSf http://localhost:12346/status | jq -r '.options.\"provided-by\"'"
+    )
+    assert "nixos-test" in out
+  '';
+})
diff --git a/nixpkgs/nixos/tests/syncthing.nix b/nixpkgs/nixos/tests/syncthing.nix
new file mode 100644
index 000000000000..aff1d8744130
--- /dev/null
+++ b/nixpkgs/nixos/tests/syncthing.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "syncthing";
+  meta.maintainers = with pkgs.lib.maintainers; [ chkno ];
+
+  nodes = rec {
+    a = {
+      environment.systemPackages = with pkgs; [ curl libxml2 syncthing ];
+      services.syncthing = {
+        enable = true;
+        openDefaultPorts = true;
+      };
+    };
+    b = a;
+  };
+
+  testScript = ''
+    import json
+    import shlex
+
+    confdir = "/var/lib/syncthing/.config/syncthing"
+
+
+    def addPeer(host, name, deviceID):
+        APIKey = host.succeed(
+            "xmllint --xpath 'string(configuration/gui/apikey)' %s/config.xml" % confdir
+        ).strip()
+        oldConf = host.succeed(
+            "curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/config" % APIKey
+        )
+        conf = json.loads(oldConf)
+        conf["devices"].append({"deviceID": deviceID, "id": name})
+        conf["folders"].append(
+            {
+                "devices": [{"deviceID": deviceID}],
+                "id": "foo",
+                "path": "/var/lib/syncthing/foo",
+                "rescanIntervalS": 1,
+            }
+        )
+        newConf = json.dumps(conf)
+        host.succeed(
+            "curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/config -X PUT -d %s"
+            % (APIKey, shlex.quote(newConf))
+        )
+
+
+    start_all()
+    a.wait_for_unit("syncthing.service")
+    b.wait_for_unit("syncthing.service")
+    a.wait_for_open_port(22000)
+    b.wait_for_open_port(22000)
+
+    aDeviceID = a.succeed("syncthing -home=%s -device-id" % confdir).strip()
+    bDeviceID = b.succeed("syncthing -home=%s -device-id" % confdir).strip()
+    addPeer(a, "b", bDeviceID)
+    addPeer(b, "a", aDeviceID)
+
+    a.wait_for_file("/var/lib/syncthing/foo")
+    b.wait_for_file("/var/lib/syncthing/foo")
+    a.succeed("echo a2b > /var/lib/syncthing/foo/a2b")
+    b.succeed("echo b2a > /var/lib/syncthing/foo/b2a")
+    a.wait_for_file("/var/lib/syncthing/foo/b2a")
+    b.wait_for_file("/var/lib/syncthing/foo/a2b")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/sysinit-reactivation.nix b/nixpkgs/nixos/tests/sysinit-reactivation.nix
new file mode 100644
index 000000000000..1a0caecb610a
--- /dev/null
+++ b/nixpkgs/nixos/tests/sysinit-reactivation.nix
@@ -0,0 +1,107 @@
+# This runs to two scenarios but in one tests:
+# - A post-sysinit service needs to be restarted AFTER tmpfiles was restarted.
+# - A service needs to be restarted BEFORE tmpfiles is restarted
+
+{ lib, ... }:
+
+let
+  makeGeneration = generation: {
+    "${generation}".configuration = {
+      systemd.services.pre-sysinit-before-tmpfiles.environment.USER =
+        lib.mkForce "${generation}-tmpfiles-user";
+
+      systemd.services.pre-sysinit-after-tmpfiles.environment = {
+        NEEDED_PATH = lib.mkForce "/run/${generation}-needed-by-pre-sysinit-after-tmpfiles";
+        PATH_TO_CREATE = lib.mkForce "/run/${generation}-needed-by-post-sysinit";
+      };
+
+      systemd.services.post-sysinit.environment = {
+        NEEDED_PATH = lib.mkForce "/run/${generation}-needed-by-post-sysinit";
+        PATH_TO_CREATE = lib.mkForce "/run/${generation}-created-by-post-sysinit";
+      };
+
+      systemd.tmpfiles.settings.test = lib.mkForce {
+        "/run/${generation}-needed-by-pre-sysinit-after-tmpfiles".f.user =
+          "${generation}-tmpfiles-user";
+      };
+    };
+  };
+in
+
+{
+
+  name = "sysinit-reactivation";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    systemd.services.pre-sysinit-before-tmpfiles = {
+      wantedBy = [ "sysinit.target" ];
+      requiredBy = [ "sysinit-reactivation.target" ];
+      before = [ "systemd-tmpfiles-setup.service" "systemd-tmpfiles-resetup.service" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      environment.USER = "tmpfiles-user";
+      script = "${pkgs.shadow}/bin/useradd $USER";
+    };
+
+    systemd.services.pre-sysinit-after-tmpfiles = {
+      wantedBy = [ "sysinit.target" ];
+      requiredBy = [ "sysinit-reactivation.target" ];
+      after = [ "systemd-tmpfiles-setup.service" "systemd-tmpfiles-resetup.service" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      environment = {
+        NEEDED_PATH = "/run/needed-by-pre-sysinit-after-tmpfiles";
+        PATH_TO_CREATE = "/run/needed-by-post-sysinit";
+      };
+      script = ''
+        if [[ -e $NEEDED_PATH ]]; then
+          touch $PATH_TO_CREATE
+        fi
+      '';
+    };
+
+    systemd.services.post-sysinit = {
+      wantedBy = [ "default.target" ];
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      environment = {
+        NEEDED_PATH = "/run/needed-by-post-sysinit";
+        PATH_TO_CREATE = "/run/created-by-post-sysinit";
+      };
+      script = ''
+        if [[ -e $NEEDED_PATH ]]; then
+          touch $PATH_TO_CREATE
+        fi
+      '';
+    };
+
+    systemd.tmpfiles.settings.test = {
+      "/run/needed-by-pre-sysinit-after-tmpfiles".f.user =
+        "tmpfiles-user";
+    };
+
+    specialisation = lib.mkMerge [
+      (makeGeneration "second")
+      (makeGeneration "third")
+    ];
+  };
+
+  testScript = { nodes, ... }: ''
+    def switch(generation):
+      toplevel = "${nodes.machine.system.build.toplevel}";
+      machine.succeed(f"{toplevel}/specialisation/{generation}/bin/switch-to-configuration switch")
+
+    machine.wait_for_unit("default.target")
+    machine.succeed("test -e /run/created-by-post-sysinit")
+
+    switch("second")
+    machine.succeed("test -e /run/second-created-by-post-sysinit")
+
+    switch("third")
+    machine.succeed("test -e /run/third-created-by-post-sysinit")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/systemd-analyze.nix b/nixpkgs/nixos/tests/systemd-analyze.nix
new file mode 100644
index 000000000000..31588e2b41aa
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-analyze.nix
@@ -0,0 +1,46 @@
+import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
+
+{
+  name = "systemd-analyze";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ raskin ];
+  };
+
+  nodes.machine =
+    { pkgs, lib, ... }:
+    { boot.kernelPackages = lib.mkIf latestKernel pkgs.linuxPackages_latest;
+      sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
+    };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    # We create a special output directory to copy it as a whole
+    with subtest("Prepare output dir"):
+        machine.succeed("mkdir systemd-analyze")
+
+
+    # Save the output into a file with given name inside the common
+    # output directory
+    def run_systemd_analyze(args, name):
+        tgt_dir = "systemd-analyze"
+        machine.succeed(
+            "systemd-analyze {} > {}/{} 2> {}/{}.err".format(
+                " ".join(args), tgt_dir, name, tgt_dir, name
+            )
+        )
+
+
+    with subtest("Print statistics"):
+        run_systemd_analyze(["blame"], "blame.txt")
+        run_systemd_analyze(["critical-chain"], "critical-chain.txt")
+        run_systemd_analyze(["dot"], "dependencies.dot")
+        run_systemd_analyze(["plot"], "systemd-analyze.svg")
+
+    # We copy the main graph into the $out (toplevel), and we also copy
+    # the entire output directory with additional data
+    with subtest("Copying the resulting data into $out"):
+        machine.copy_from_vm("systemd-analyze/", "")
+        machine.copy_from_vm("systemd-analyze/systemd-analyze.svg", "")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-binfmt.nix b/nixpkgs/nixos/tests/systemd-binfmt.nix
new file mode 100644
index 000000000000..b16fda0ddb1a
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-binfmt.nix
@@ -0,0 +1,90 @@
+# Teach the kernel how to run armv7l and aarch64-linux binaries,
+# and run GNU Hello for these architectures.
+
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  expectArgv0 = xpkgs: xpkgs.runCommandCC "expect-argv0" {
+    src = pkgs.writeText "expect-argv0.c" ''
+      #include <stdio.h>
+      #include <string.h>
+
+      int main(int argc, char **argv) {
+        fprintf(stderr, "Our argv[0] is %s\n", argv[0]);
+
+        if (strcmp(argv[0], argv[1])) {
+          fprintf(stderr, "ERROR: argv[0] is %s, should be %s\n", argv[0], argv[1]);
+          return 1;
+        }
+
+        return 0;
+      }
+    '';
+  } ''
+    $CC -o $out $src
+  '';
+in {
+  basic = makeTest {
+    name = "systemd-binfmt";
+    nodes.machine = {
+      boot.binfmt.emulatedSystems = [
+        "armv7l-linux"
+        "aarch64-linux"
+      ];
+    };
+
+    testScript = let
+      helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello;
+      helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello;
+    in ''
+      machine.start()
+
+      assert "world" in machine.succeed(
+          "${helloArmv7l}/bin/hello"
+      )
+
+      assert "world" in machine.succeed(
+          "${helloAarch64}/bin/hello"
+      )
+    '';
+  };
+
+  preserveArgvZero = makeTest {
+    name = "systemd-binfmt-preserve-argv0";
+    nodes.machine = {
+      boot.binfmt.emulatedSystems = [
+        "aarch64-linux"
+      ];
+    };
+    testScript = let
+      testAarch64 = expectArgv0 pkgs.pkgsCross.aarch64-multiplatform;
+    in ''
+      machine.start()
+      machine.succeed("exec -a meow ${testAarch64} meow")
+    '';
+  };
+
+  ldPreload = makeTest {
+    name = "systemd-binfmt-ld-preload";
+    nodes.machine = {
+      boot.binfmt.emulatedSystems = [
+        "aarch64-linux"
+      ];
+    };
+    testScript = let
+      helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello;
+      libredirectAarch64 = pkgs.pkgsCross.aarch64-multiplatform.libredirect;
+    in ''
+      machine.start()
+
+      assert "error" not in machine.succeed(
+          "LD_PRELOAD='${libredirectAarch64}/lib/libredirect.so' ${helloAarch64}/bin/hello 2>&1"
+      ).lower()
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch b/nixpkgs/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch
new file mode 100644
index 000000000000..ef547c02f918
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch
@@ -0,0 +1,25 @@
+From d87a7513c6f2f2824203032ef27caeb84892ed7e Mon Sep 17 00:00:00 2001
+From: Will Fancher <elvishjerricco@gmail.com>
+Date: Tue, 30 May 2023 16:53:20 -0400
+Subject: [PATCH] Intentionally break the fat driver
+
+---
+ FatPkg/EnhancedFatDxe/ReadWrite.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/FatPkg/EnhancedFatDxe/ReadWrite.c b/FatPkg/EnhancedFatDxe/ReadWrite.c
+index 8f525044d1f1..32c62ff7817b 100644
+--- a/FatPkg/EnhancedFatDxe/ReadWrite.c
++++ b/FatPkg/EnhancedFatDxe/ReadWrite.c
+@@ -216,6 +216,11 @@ FatIFileAccess (
+   Volume = OFile->Volume;

+   Task   = NULL;

+ 

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

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

++    return EFI_BAD_BUFFER_SIZE;

++  }

++

+   //

+   // Write to a directory is unsupported

+   //

diff --git a/nixpkgs/nixos/tests/systemd-boot.nix b/nixpkgs/nixos/tests/systemd-boot.nix
new file mode 100644
index 000000000000..ce3245f3d862
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-boot.nix
@@ -0,0 +1,351 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  common = {
+    virtualisation.useBootLoader = true;
+    virtualisation.useEFIBoot = true;
+    boot.loader.systemd-boot.enable = true;
+    boot.loader.efi.canTouchEfiVariables = true;
+    environment.systemPackages = [ pkgs.efibootmgr ];
+  };
+in
+{
+  basic = makeTest {
+    name = "systemd-boot";
+    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
+
+    nodes.machine = common;
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+
+      # Ensure we actually booted using systemd-boot
+      # Magic number is the vendor UUID used by systemd-boot.
+      machine.succeed(
+          "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+      )
+
+      # "bootctl install" should have created an EFI entry
+      machine.succeed('efibootmgr | grep "Linux Boot Manager"')
+    '';
+  };
+
+  # Test that systemd-boot works with secure boot
+  secureBoot = makeTest {
+    name = "systemd-boot-secure-boot";
+
+    nodes.machine = {
+      imports = [ common ];
+      environment.systemPackages = [ pkgs.sbctl ];
+      virtualisation.useSecureBoot = true;
+    };
+
+    testScript = ''
+      machine.start(allow_reboot=True)
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("sbctl create-keys")
+      machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine")
+      machine.succeed('sbctl sign /boot/EFI/systemd/systemd-bootx64.efi')
+      machine.succeed('sbctl sign /boot/EFI/BOOT/BOOTX64.EFI')
+      machine.succeed('sbctl sign /boot/EFI/nixos/*bzImage.efi')
+
+      machine.reboot()
+
+      assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
+    '';
+  };
+
+  # Check that specialisations create corresponding boot entries.
+  specialisation = makeTest {
+    name = "systemd-boot-specialisation";
+    meta.maintainers = with pkgs.lib.maintainers; [ lukegb julienmalka ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      specialisation.something.configuration = {};
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed(
+          "test -e /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
+      )
+      machine.succeed(
+          "grep -q 'title NixOS (something)' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
+      )
+    '';
+  };
+
+  # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI"
+  fallback = makeTest {
+    name = "systemd-boot-fallback";
+    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      boot.loader.efi.canTouchEfiVariables = mkForce false;
+    };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+
+      # Ensure we actually booted using systemd-boot
+      # Magic number is the vendor UUID used by systemd-boot.
+      machine.succeed(
+          "test -e /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+      )
+
+      # "bootctl install" should _not_ have created an EFI entry
+      machine.fail('efibootmgr | grep "Linux Boot Manager"')
+    '';
+  };
+
+  update = makeTest {
+    name = "systemd-boot-update";
+    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
+
+    nodes.machine = common;
+
+    testScript = ''
+      machine.succeed("mount -o remount,rw /boot")
+
+      # Replace version inside sd-boot with something older. See magic[] string in systemd src/boot/efi/boot.c
+      machine.succeed(
+          """
+        find /boot -iname '*boot*.efi' -print0 | \
+        xargs -0 -I '{}' sed -i 's/#### LoaderInfo: systemd-boot .* ####/#### LoaderInfo: systemd-boot 000.0-1-notnixos ####/' '{}'
+      """
+      )
+
+      output = machine.succeed("/run/current-system/bin/switch-to-configuration boot")
+      assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message"
+    '';
+  };
+
+  memtest86 = makeTest {
+    name = "systemd-boot-memtest86";
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      boot.loader.systemd-boot.memtest86.enable = true;
+    };
+
+    testScript = ''
+      machine.succeed("test -e /boot/loader/entries/memtest86.conf")
+      machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
+    '';
+  };
+
+  netbootxyz = makeTest {
+    name = "systemd-boot-netbootxyz";
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      boot.loader.systemd-boot.netbootxyz.enable = true;
+    };
+
+    testScript = ''
+      machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf")
+      machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
+    '';
+  };
+
+  entryFilename = makeTest {
+    name = "systemd-boot-entry-filename";
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      boot.loader.systemd-boot.memtest86.enable = true;
+      boot.loader.systemd-boot.memtest86.entryFilename = "apple.conf";
+    };
+
+    testScript = ''
+      machine.fail("test -e /boot/loader/entries/memtest86.conf")
+      machine.succeed("test -e /boot/loader/entries/apple.conf")
+      machine.succeed("test -e /boot/efi/memtest86/memtest.efi")
+    '';
+  };
+
+  extraEntries = makeTest {
+    name = "systemd-boot-extra-entries";
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      boot.loader.systemd-boot.extraEntries = {
+        "banana.conf" = ''
+          title banana
+        '';
+      };
+    };
+
+    testScript = ''
+      machine.succeed("test -e /boot/loader/entries/banana.conf")
+      machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/banana.conf")
+    '';
+  };
+
+  extraFiles = makeTest {
+    name = "systemd-boot-extra-files";
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+
+    nodes.machine = { pkgs, lib, ... }: {
+      imports = [ common ];
+      boot.loader.systemd-boot.extraFiles = {
+        "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
+      };
+    };
+
+    testScript = ''
+      machine.succeed("test -e /boot/efi/fruits/tomato.efi")
+      machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+    '';
+  };
+
+  switch-test = makeTest {
+    name = "systemd-boot-switch-test";
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+
+    nodes = {
+      inherit common;
+
+      machine = { pkgs, nodes, ... }: {
+        imports = [ common ];
+        boot.loader.systemd-boot.extraFiles = {
+          "efi/fruits/tomato.efi" = pkgs.netbootxyz-efi;
+        };
+
+        # These are configs for different nodes, but we'll use them here in `machine`
+        system.extraDependencies = [
+          nodes.common.system.build.toplevel
+          nodes.with_netbootxyz.system.build.toplevel
+        ];
+      };
+
+      with_netbootxyz = { pkgs, ... }: {
+        imports = [ common ];
+        boot.loader.systemd-boot.netbootxyz.enable = true;
+      };
+    };
+
+    testScript = { nodes, ... }: let
+      originalSystem = nodes.machine.system.build.toplevel;
+      baseSystem = nodes.common.system.build.toplevel;
+      finalSystem = nodes.with_netbootxyz.system.build.toplevel;
+    in ''
+      machine.succeed("test -e /boot/efi/fruits/tomato.efi")
+      machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+
+      with subtest("remove files when no longer needed"):
+          machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
+          machine.fail("test -e /boot/efi/fruits/tomato.efi")
+          machine.fail("test -d /boot/efi/fruits")
+          machine.succeed("test -d /boot/efi/nixos/.extra-files")
+          machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+          machine.fail("test -d /boot/efi/nixos/.extra-files/efi/fruits")
+
+      with subtest("files are added back when needed again"):
+          machine.succeed("${originalSystem}/bin/switch-to-configuration boot")
+          machine.succeed("test -e /boot/efi/fruits/tomato.efi")
+          machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+
+      with subtest("simultaneously removing and adding files works"):
+          machine.succeed("${finalSystem}/bin/switch-to-configuration boot")
+          machine.fail("test -e /boot/efi/fruits/tomato.efi")
+          machine.fail("test -e /boot/efi/nixos/.extra-files/efi/fruits/tomato.efi")
+          machine.succeed("test -e /boot/loader/entries/o_netbootxyz.conf")
+          machine.succeed("test -e /boot/efi/netbootxyz/netboot.xyz.efi")
+          machine.succeed("test -e /boot/efi/nixos/.extra-files/loader/entries/o_netbootxyz.conf")
+          machine.succeed("test -e /boot/efi/nixos/.extra-files/efi/netbootxyz/netboot.xyz.efi")
+    '';
+  };
+
+  garbage-collect-entry = makeTest {
+    name = "systemd-boot-garbage-collect-entry";
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+
+    nodes = {
+      inherit common;
+      machine = { pkgs, nodes, ... }: {
+        imports = [ common ];
+
+        # These are configs for different nodes, but we'll use them here in `machine`
+        system.extraDependencies = [
+          nodes.common.system.build.toplevel
+        ];
+      };
+    };
+
+    testScript = { nodes, ... }:
+      let
+        baseSystem = nodes.common.system.build.toplevel;
+      in
+      ''
+        machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}")
+        machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1")
+        machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
+        machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf")
+        machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf")
+      '';
+  };
+
+  # Some UEFI firmwares fail on large reads. Now that systemd-boot loads initrd
+  # itself, systems with such firmware won't boot without this fix
+  uefiLargeFileWorkaround = makeTest {
+    name = "uefi-large-file-workaround";
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+    nodes.machine = { pkgs, ... }: {
+      imports = [common];
+      virtualisation.efi.OVMF = pkgs.OVMF.overrideAttrs (old: {
+        # This patch deliberately breaks the FAT driver in EDK2 to
+        # exhibit (part of) the firmware bug that we are testing
+        # for. Files greater than 10MiB will fail to be read in a
+        # single Read() call, so systemd-boot will fail to load the
+        # initrd without a workaround. The number 10MiB was chosen
+        # because if it were smaller than the kernel size, even the
+        # LoadImage call would fail, which is not the failure mode
+        # we're testing for. It needs to be between the kernel size
+        # and the initrd size.
+        patches = old.patches or [] ++ [ ./systemd-boot-ovmf-broken-fat-driver.patch ];
+      });
+    };
+
+    testScript = ''
+      machine.wait_for_unit("multi-user.target")
+    '';
+  };
+
+  no-bootspec = makeTest
+    {
+      name = "systemd-boot-no-bootspec";
+      meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+
+      nodes.machine = {
+        imports = [ common ];
+        boot.bootspec.enable = false;
+      };
+
+      testScript = ''
+        machine.start()
+        machine.wait_for_unit("multi-user.target")
+      '';
+    };
+}
diff --git a/nixpkgs/nixos/tests/systemd-bpf.nix b/nixpkgs/nixos/tests/systemd-bpf.nix
new file mode 100644
index 000000000000..e11347a2a817
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-bpf.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-bpf";
+  meta = with lib.maintainers; {
+    maintainers = [ veehaitch ];
+  };
+  nodes = {
+    node1 = {
+      virtualisation.vlans = [ 1 ];
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1.ipv4.addresses = [
+          { address = "192.168.1.1"; prefixLength = 24; }
+        ];
+      };
+    };
+
+    node2 = {
+      virtualisation.vlans = [ 1 ];
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1.ipv4.addresses = [
+          { address = "192.168.1.2"; prefixLength = 24; }
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    node1.wait_for_unit("systemd-networkd-wait-online.service")
+    node2.wait_for_unit("systemd-networkd-wait-online.service")
+
+    with subtest("test RestrictNetworkInterfaces= works"):
+      node1.succeed("ping -c 5 192.168.1.2")
+      node1.succeed("systemd-run -t -p RestrictNetworkInterfaces='eth1' ping -c 5 192.168.1.2")
+      node1.fail("systemd-run -t -p RestrictNetworkInterfaces='lo' ping -c 5 192.168.1.2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-confinement.nix b/nixpkgs/nixos/tests/systemd-confinement.nix
new file mode 100644
index 000000000000..428888d41a20
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-confinement.nix
@@ -0,0 +1,184 @@
+import ./make-test-python.nix {
+  name = "systemd-confinement";
+
+  nodes.machine = { pkgs, lib, ... }: let
+    testServer = pkgs.writeScript "testserver.sh" ''
+      #!${pkgs.runtimeShell}
+      export PATH=${lib.escapeShellArg "${pkgs.coreutils}/bin"}
+      ${lib.escapeShellArg pkgs.runtimeShell} 2>&1
+      echo "exit-status:$?"
+    '';
+
+    testClient = pkgs.writeScriptBin "chroot-exec" ''
+      #!${pkgs.runtimeShell} -e
+      output="$(echo "$@" | nc -NU "/run/test$(< /teststep).sock")"
+      ret="$(echo "$output" | sed -nre '$s/^exit-status:([0-9]+)$/\1/p')"
+      echo "$output" | head -n -1
+      exit "''${ret:-1}"
+    '';
+
+    mkTestStep = num: {
+      testScript,
+      config ? {},
+      serviceName ? "test${toString num}",
+    }: {
+      systemd.sockets.${serviceName} = {
+        description = "Socket for Test Service ${toString num}";
+        wantedBy = [ "sockets.target" ];
+        socketConfig.ListenStream = "/run/test${toString num}.sock";
+        socketConfig.Accept = true;
+      };
+
+      systemd.services."${serviceName}@" = {
+        description = "Confined Test Service ${toString num}";
+        confinement = (config.confinement or {}) // { enable = true; };
+        serviceConfig = (config.serviceConfig or {}) // {
+          ExecStart = testServer;
+          StandardInput = "socket";
+        };
+      } // removeAttrs config [ "confinement" "serviceConfig" ];
+
+      __testSteps = lib.mkOrder num (''
+        machine.succeed("echo ${toString num} > /teststep")
+      '' + testScript);
+    };
+
+  in {
+    imports = lib.imap1 mkTestStep [
+      { config.confinement.mode = "chroot-only";
+        testScript = ''
+          with subtest("chroot-only confinement"):
+              paths = machine.succeed('chroot-exec ls -1 / | paste -sd,').strip()
+              assert_eq(paths, "bin,nix,run")
+              uid = machine.succeed('chroot-exec id -u').strip()
+              assert_eq(uid, "0")
+              machine.succeed("chroot-exec chown 65534 /bin")
+        '';
+      }
+      { testScript = ''
+          with subtest("full confinement with APIVFS"):
+              machine.fail("chroot-exec ls -l /etc")
+              machine.fail("chroot-exec chown 65534 /bin")
+              assert_eq(machine.succeed('chroot-exec id -u').strip(), "0")
+              machine.succeed("chroot-exec chown 0 /bin")
+        '';
+      }
+      { config.serviceConfig.BindReadOnlyPaths = [ "/etc" ];
+        testScript = ''
+          with subtest("check existence of bind-mounted /etc"):
+              passwd = machine.succeed('chroot-exec cat /etc/passwd').strip()
+              assert len(passwd) > 0, "/etc/passwd must not be empty"
+        '';
+      }
+      { config.serviceConfig.User = "chroot-testuser";
+        config.serviceConfig.Group = "chroot-testgroup";
+        testScript = ''
+          with subtest("check if User/Group really runs as non-root"):
+              machine.succeed("chroot-exec ls -l /dev")
+              uid = machine.succeed('chroot-exec id -u').strip()
+              assert uid != "0", "UID of chroot-testuser shouldn't be 0"
+              machine.fail("chroot-exec touch /bin/test")
+        '';
+      }
+      (let
+        symlink = pkgs.runCommand "symlink" {
+          target = pkgs.writeText "symlink-target" "got me\n";
+        } "ln -s \"$target\" \"$out\"";
+      in {
+        config.confinement.packages = lib.singleton symlink;
+        testScript = ''
+          with subtest("check if symlinks are properly bind-mounted"):
+              machine.fail("chroot-exec test -e /etc")
+              text = machine.succeed('chroot-exec cat ${symlink}').strip()
+              assert_eq(text, "got me")
+        '';
+      })
+      { config.serviceConfig.User = "chroot-testuser";
+        config.serviceConfig.Group = "chroot-testgroup";
+        config.serviceConfig.StateDirectory = "testme";
+        testScript = ''
+          with subtest("check if StateDirectory works"):
+              machine.succeed("chroot-exec touch /tmp/canary")
+              machine.succeed('chroot-exec "echo works > /var/lib/testme/foo"')
+              machine.succeed('test "$(< /var/lib/testme/foo)" = works')
+              machine.succeed("test ! -e /tmp/canary")
+        '';
+      }
+      { testScript = ''
+          with subtest("check if /bin/sh works"):
+              machine.succeed(
+                  "chroot-exec test -e /bin/sh",
+                  'test "$(chroot-exec \'/bin/sh -c "echo bar"\')" = bar',
+              )
+        '';
+      }
+      { config.confinement.binSh = null;
+        testScript = ''
+          with subtest("check if suppressing /bin/sh works"):
+              machine.succeed("chroot-exec test ! -e /bin/sh")
+              machine.succeed('test "$(chroot-exec \'/bin/sh -c "echo foo"\')" != foo')
+        '';
+      }
+      { config.confinement.binSh = "${pkgs.hello}/bin/hello";
+        testScript = ''
+          with subtest("check if we can set /bin/sh to something different"):
+              machine.succeed("chroot-exec test -e /bin/sh")
+              machine.succeed('test "$(chroot-exec /bin/sh -g foo)" = foo')
+        '';
+      }
+      { config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n";
+        testScript = ''
+          with subtest("check if only Exec* dependencies are included"):
+              machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" != eek')
+        '';
+      }
+      { config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n";
+        config.confinement.fullUnit = true;
+        testScript = ''
+          with subtest("check if all unit dependencies are included"):
+              machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" = eek')
+        '';
+      }
+      { serviceName = "shipped-unitfile";
+        config.confinement.mode = "chroot-only";
+        testScript = ''
+          with subtest("check if shipped unit file still works"):
+              machine.succeed(
+                  'chroot-exec \'kill -9 $$ 2>&1 || :\' | '
+                  'grep -q "Too many levels of symbolic links"'
+              )
+        '';
+      }
+    ];
+
+    options.__testSteps = lib.mkOption {
+      type = lib.types.lines;
+      description = lib.mdDoc "All of the test steps combined as a single script.";
+    };
+
+    config.environment.systemPackages = lib.singleton testClient;
+    config.systemd.packages = lib.singleton (pkgs.writeTextFile {
+      name = "shipped-unitfile";
+      destination = "/etc/systemd/system/shipped-unitfile@.service";
+      text = ''
+        [Service]
+        SystemCallFilter=~kill
+        SystemCallErrorNumber=ELOOP
+      '';
+    });
+
+    config.users.groups.chroot-testgroup = {};
+    config.users.users.chroot-testuser = {
+      isSystemUser = true;
+      description = "Chroot Test User";
+      group = "chroot-testgroup";
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    def assert_eq(a, b):
+        assert a == b, f"{a} != {b}"
+
+    machine.wait_for_unit("multi-user.target")
+  '' + nodes.machine.config.__testSteps;
+}
diff --git a/nixpkgs/nixos/tests/systemd-coredump.nix b/nixpkgs/nixos/tests/systemd-coredump.nix
new file mode 100644
index 000000000000..62137820878b
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-coredump.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+
+  crasher = pkgs.writeCBin "crasher" "int main;";
+
+  commonConfig = {
+    systemd.services.crasher.serviceConfig = {
+      ExecStart = "${crasher}/bin/crasher";
+      StateDirectory = "crasher";
+      WorkingDirectory = "%S/crasher";
+      Restart = "no";
+    };
+  };
+
+in
+
+{
+  name = "systemd-coredump";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ squalus ];
+  };
+
+  nodes.machine1 = { pkgs, lib, ... }: commonConfig;
+  nodes.machine2 = { pkgs, lib, ... }: lib.recursiveUpdate commonConfig {
+    systemd.coredump.enable = false;
+    systemd.package = pkgs.systemd.override {
+      withCoredump = false;
+    };
+  };
+
+  testScript = ''
+    with subtest("systemd-coredump enabled"):
+      machine1.wait_for_unit("multi-user.target")
+      machine1.wait_for_unit("systemd-coredump.socket")
+      machine1.systemctl("start crasher");
+      machine1.wait_until_succeeds("coredumpctl list | grep crasher", timeout=10)
+      machine1.fail("stat /var/lib/crasher/core")
+
+    with subtest("systemd-coredump disabled"):
+      machine2.systemctl("start crasher");
+      machine2.wait_until_succeeds("stat /var/lib/crasher/core", timeout=10)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-credentials-tpm2.nix b/nixpkgs/nixos/tests/systemd-credentials-tpm2.nix
new file mode 100644
index 000000000000..bf7418312236
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-credentials-tpm2.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "systemd-credentials-tpm2";
+
+  meta = {
+    maintainers = with pkgs.lib.maintainers; [ tmarkus ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.tpm.enable = true;
+    environment.systemPackages = with pkgs; [ diffutils ];
+  };
+
+  testScript = ''
+    CRED_NAME = "testkey"
+    CRED_RAW_FILE = f"/root/{CRED_NAME}"
+    CRED_FILE = f"/root/{CRED_NAME}.cred"
+
+    def systemd_run(machine, cmd):
+        machine.log(f"Executing command (via systemd-run): \"{cmd}\"")
+
+        (status, out) = machine.execute( " ".join([
+            "systemd-run",
+            "--service-type=exec",
+            "--quiet",
+            "--wait",
+            "-E PATH=\"$PATH\"",
+            "-p StandardOutput=journal",
+            "-p StandardError=journal",
+            f"-p LoadCredentialEncrypted={CRED_NAME}:{CRED_FILE}",
+            f"$SHELL -c '{cmd}'"
+            ]) )
+
+        if status != 0:
+            raise Exception(f"systemd_run failed (status {status})")
+
+        machine.log("systemd-run finished successfully")
+
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Check whether TPM device exists"):
+        machine.succeed("test -e /dev/tpm0")
+        machine.succeed("test -e /dev/tpmrm0")
+
+    with subtest("Check whether systemd-creds detects TPM2 correctly"):
+        cmd = "systemd-creds has-tpm2"
+        machine.log(f"Running \"{cmd}\"")
+        (status, _) = machine.execute(cmd)
+
+        # Check exit code equals 0 or 1 (1 means firmware support is missing, which is OK here)
+        if status != 0 and status != 1:
+            raise Exception("systemd-creds failed to detect TPM2")
+
+    with subtest("Encrypt credential using systemd-creds"):
+        machine.succeed(f"dd if=/dev/urandom of={CRED_RAW_FILE} bs=1k count=16")
+        machine.succeed(f"systemd-creds --with-key=host+tpm2 encrypt --name=testkey {CRED_RAW_FILE} {CRED_FILE}")
+
+    with subtest("Write provided credential and check for equality"):
+        CRED_OUT_FILE = f"/root/{CRED_NAME}.out"
+        systemd_run(machine, f"systemd-creds cat testkey > {CRED_OUT_FILE}")
+        machine.succeed(f"cmp --silent -- {CRED_RAW_FILE} {CRED_OUT_FILE}")
+
+    with subtest("Check whether systemd service can see credential in systemd-creds list"):
+        systemd_run(machine, f"systemd-creds list | grep {CRED_NAME}")
+
+    with subtest("Check whether systemd service can access credential in $CREDENTIALS_DIRECTORY"):
+        systemd_run(machine, f"cmp --silent -- $CREDENTIALS_DIRECTORY/{CRED_NAME} {CRED_RAW_FILE}")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-cryptenroll.nix b/nixpkgs/nixos/tests/systemd-cryptenroll.nix
new file mode 100644
index 000000000000..034aae1d5e95
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-cryptenroll.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "systemd-cryptenroll";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ymatsiuk ];
+  };
+
+  nodes.machine = { pkgs, lib, ... }: {
+    environment.systemPackages = [ pkgs.cryptsetup ];
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      tpm.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.start()
+
+    # Verify the TPM device is available and accessible by systemd-cryptenroll
+    machine.succeed("test -e /dev/tpm0")
+    machine.succeed("test -e /dev/tpmrm0")
+    machine.succeed("systemd-cryptenroll --tpm2-device=list")
+
+    # Create LUKS partition
+    machine.succeed("echo -n lukspass | cryptsetup luksFormat -q /dev/vdb -")
+    # Enroll new LUKS key and bind it to Secure Boot state
+    # For more details on PASSWORD variable, check the following issue:
+    # https://github.com/systemd/systemd/issues/20955
+    machine.succeed("PASSWORD=lukspass systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/vdb")
+    # Add LUKS partition to /etc/crypttab to test auto unlock
+    machine.succeed("echo 'luks /dev/vdb - tpm2-device=auto' >> /etc/crypttab")
+
+    machine.shutdown()
+    machine.start()
+
+    # Test LUKS partition automatic unlock on boot
+    machine.wait_for_unit("systemd-cryptsetup@luks.service")
+    # Wipe TPM2 slot
+    machine.succeed("systemd-cryptenroll --wipe-slot=tpm2 /dev/vdb")
+  '';
+})
+
diff --git a/nixpkgs/nixos/tests/systemd-escaping.nix b/nixpkgs/nixos/tests/systemd-escaping.nix
new file mode 100644
index 000000000000..29d2ed1aa352
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-escaping.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  echoAll = pkgs.writeScript "echo-all" ''
+    #! ${pkgs.runtimeShell}
+    for s in "$@"; do
+      printf '%s\n' "$s"
+    done
+  '';
+  # deliberately using a local empty file instead of pkgs.emptyFile to have
+  # a non-store path in the test
+  args = [ "a%Nything" "lang=\${LANG}" ";" "/bin/sh -c date" ./empty-file 4.2 23 ];
+in
+{
+  name = "systemd-escaping";
+
+  nodes.machine = { pkgs, lib, utils, ... }: {
+    systemd.services.echo =
+      assert !(builtins.tryEval (utils.escapeSystemdExecArgs [ [] ])).success;
+      assert !(builtins.tryEval (utils.escapeSystemdExecArgs [ {} ])).success;
+      assert !(builtins.tryEval (utils.escapeSystemdExecArgs [ null ])).success;
+      assert !(builtins.tryEval (utils.escapeSystemdExecArgs [ false ])).success;
+      assert !(builtins.tryEval (utils.escapeSystemdExecArgs [ (_:_) ])).success;
+      { description = "Echo to the journal";
+        serviceConfig.Type = "oneshot";
+        serviceConfig.ExecStart = ''
+          ${echoAll} ${utils.escapeSystemdExecArgs args}
+        '';
+      };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("systemctl start echo.service")
+    # skip the first 'Starting <service> ...' line
+    logs = machine.succeed("journalctl -u echo.service -o cat").splitlines()[1:]
+    assert "a%Nything" == logs[0]
+    assert "lang=''${LANG}" == logs[1]
+    assert ";" == logs[2]
+    assert "/bin/sh -c date" == logs[3]
+    assert "/nix/store/ij3gw72f4n5z4dz6nnzl1731p9kmjbwr-empty-file" == logs[4]
+    assert "4.2" in logs[5] # toString produces extra fractional digits!
+    assert "23" == logs[6]
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-homed.nix b/nixpkgs/nixos/tests/systemd-homed.nix
new file mode 100644
index 000000000000..ecc92e98eddc
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-homed.nix
@@ -0,0 +1,99 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  password = "foobar";
+  newPass = "barfoo";
+in
+{
+  name = "systemd-homed";
+  nodes.machine = { config, pkgs, ... }: {
+    services.homed.enable = true;
+
+    users.users.test-normal-user = {
+      extraGroups = [ "wheel" ];
+      isNormalUser = true;
+      initialPassword = password;
+    };
+  };
+  testScript = ''
+    def switchTTY(number):
+      machine.send_key(f"alt-f{number}")
+      machine.wait_until_succeeds(f"[ $(fgconsole) = {number} ]")
+      machine.wait_for_unit(f"getty@tty{number}.service")
+      machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{number}'")
+
+    machine.wait_for_unit("multi-user.target")
+
+    # Smoke test to make sure the pam changes didn't break regular users.
+    machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    with subtest("login as regular user"):
+      switchTTY(2)
+      machine.wait_until_tty_matches("2", "login: ")
+      machine.send_chars("test-normal-user\n")
+      machine.wait_until_tty_matches("2", "login: test-normal-user")
+      machine.wait_until_tty_matches("2", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -u test-normal-user bash")
+      machine.send_chars("whoami > /tmp/1\n")
+      machine.wait_for_file("/tmp/1")
+      assert "test-normal-user" in machine.succeed("cat /tmp/1")
+
+    with subtest("create homed encrypted user"):
+      # TODO: Figure out how to pass password manually.
+      #
+      # This environment variable is used for homed internal testing
+      # and is not documented.
+      machine.succeed("NEWPASSWORD=${password} homectl create --shell=/run/current-system/sw/bin/bash --storage=luks -G wheel test-homed-user")
+
+    with subtest("login as homed user"):
+      switchTTY(3)
+      machine.wait_until_tty_matches("3", "login: ")
+      machine.send_chars("test-homed-user\n")
+      machine.wait_until_tty_matches("3", "login: test-homed-user")
+      machine.wait_until_tty_matches("3", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -t tty3 -u test-homed-user bash")
+      machine.send_chars("whoami > /tmp/2\n")
+      machine.wait_for_file("/tmp/2")
+      assert "test-homed-user" in machine.succeed("cat /tmp/2")
+
+    with subtest("change homed user password"):
+      switchTTY(4)
+      machine.wait_until_tty_matches("4", "login: ")
+      machine.send_chars("test-homed-user\n")
+      machine.wait_until_tty_matches("4", "login: test-homed-user")
+      machine.wait_until_tty_matches("4", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -t tty4 -u test-homed-user bash")
+      machine.send_chars("passwd\n")
+      # homed does it in a weird order, it asks for new passes, then it asks
+      # for the old one.
+      machine.sleep(2)
+      machine.send_chars("${newPass}\n")
+      machine.sleep(2)
+      machine.send_chars("${newPass}\n")
+      machine.sleep(4)
+      machine.send_chars("${password}\n")
+      machine.wait_until_fails("pgrep -t tty4 passwd")
+
+      @polling_condition
+      def not_logged_in_tty5():
+        machine.fail("pgrep -t tty5 bash")
+
+      switchTTY(5)
+      with not_logged_in_tty5: # type: ignore[union-attr]
+        machine.wait_until_tty_matches("5", "login: ")
+        machine.send_chars("test-homed-user\n")
+        machine.wait_until_tty_matches("5", "login: test-homed-user")
+        machine.wait_until_tty_matches("5", "Password: ")
+        machine.send_chars("${password}\n")
+        machine.wait_until_tty_matches("5", "Password incorrect or not sufficient for authentication of user test-homed-user.")
+        machine.wait_until_tty_matches("5", "Sorry, try again: ")
+      machine.send_chars("${newPass}\n")
+      machine.send_chars("whoami > /tmp/4\n")
+      machine.wait_for_file("/tmp/4")
+      assert "test-homed-user" in machine.succeed("cat /tmp/4")
+
+    with subtest("homed user should be in wheel according to NSS"):
+      machine.succeed("userdbctl group wheel -s io.systemd.NameServiceSwitch | grep test-homed-user")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-bridge.nix b/nixpkgs/nixos/tests/systemd-initrd-bridge.nix
new file mode 100644
index 000000000000..f48a46ff2b93
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-bridge.nix
@@ -0,0 +1,63 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-initrd-bridge";
+  meta.maintainers = [ lib.maintainers.majiir ];
+
+  # Tests bridge interface configuration in systemd-initrd.
+  #
+  # The 'a' and 'b' nodes are connected to a 'bridge' node through different
+  # links. The 'bridge' node configures a bridge across them. It waits forever
+  # in initrd (stage 1) with networking enabled. 'a' and 'b' ping 'bridge' to
+  # test connectivity with the bridge interface. Then, 'a' pings 'b' to test
+  # the bridge itself.
+
+  nodes = {
+    bridge = { config, lib, ... }: {
+      boot.initrd.systemd.enable = true;
+      boot.initrd.network.enable = true;
+      boot.initrd.systemd.services.boot-blocker = {
+        before = [ "initrd.target" ];
+        wantedBy = [ "initrd.target" ];
+        script = "sleep infinity";
+        serviceConfig.Type = "oneshot";
+      };
+
+      networking.primaryIPAddress = "192.168.1.${toString config.virtualisation.test.nodeNumber}";
+
+      virtualisation.vlans = [ 1 2 ];
+      networking.bridges.br0.interfaces = [ "eth1" "eth2" ];
+
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkForce [];
+        eth2.ipv4.addresses = lib.mkForce [];
+        br0.ipv4.addresses = [{
+          address = config.networking.primaryIPAddress;
+          prefixLength = 24;
+        }];
+      };
+    };
+
+    a = {
+      virtualisation.vlans = [ 1 ];
+    };
+
+    b = { config, ... }: {
+      virtualisation.vlans = [ 2 ];
+      networking.primaryIPAddress = lib.mkForce "192.168.1.${toString config.virtualisation.test.nodeNumber}";
+      networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
+        address = config.networking.primaryIPAddress;
+        prefixLength = 24;
+      }];
+    };
+  };
+
+  testScript = ''
+    start_all()
+    a.wait_for_unit("network.target")
+    b.wait_for_unit("network.target")
+
+    a.succeed("ping -n -w 10 -c 1 bridge >&2")
+    b.succeed("ping -n -w 10 -c 1 bridge >&2")
+
+    a.succeed("ping -n -w 10 -c 1 b >&2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix b/nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix
new file mode 100644
index 000000000000..9196033789cb
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-btrfs-raid.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-btrfs-raid";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 512 ];
+      useBootLoader = true;
+      # Booting off the BTRFS RAID requires an available init script from the Nix store
+      mountHostNixStore = true;
+      useEFIBoot = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+    boot.loader.efi.canTouchEfiVariables = true;
+
+    environment.systemPackages = with pkgs; [ btrfs-progs ];
+    boot.initrd.systemd = {
+      enable = true;
+      emergencyAccess = true;
+    };
+
+    specialisation.boot-btrfs-raid.configuration = {
+      fileSystems = lib.mkVMOverride {
+        "/".fsType = lib.mkForce "btrfs";
+      };
+      virtualisation.rootDevice = "/dev/vdb";
+    };
+  };
+
+  testScript = ''
+    # Create RAID
+    machine.succeed("mkfs.btrfs -d raid0 /dev/vdb /dev/vdc")
+    machine.succeed("mkdir -p /mnt && mount /dev/vdb /mnt && echo hello > /mnt/test && umount /mnt")
+
+    # Boot from the RAID
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-btrfs-raid.conf")
+    machine.succeed("sync")
+    machine.crash()
+    machine.wait_for_unit("multi-user.target")
+
+    # Ensure we have successfully booted from the RAID
+    assert "(initrd)" in machine.succeed("systemd-analyze")  # booted with systemd in stage 1
+    assert "/dev/vdb on / type btrfs" in machine.succeed("mount")
+    assert "hello" in machine.succeed("cat /test")
+    assert "Total devices 2" in machine.succeed("btrfs filesystem show")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-fido2.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-fido2.nix
new file mode 100644
index 000000000000..f9f75ab7f301
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-fido2.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-luks-fido2";
+
+  nodes.machine = { pkgs, config, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = true;
+      # Booting off the encrypted disk requires having a Nix store available for the init script
+      mountHostNixStore = true;
+      useEFIBoot = true;
+      qemu.package = lib.mkForce (pkgs.qemu_test.override { canokeySupport = true; });
+      qemu.options = [ "-device canokey,file=/tmp/canokey-file" ];
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.initrd.systemd.enable = true;
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+
+    specialisation.boot-luks.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          crypttabExtraOpts = [ "fido2-device=auto" ];
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      virtualisation.fileSystems."/".autoFormat = true;
+    };
+  };
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --fido2-device=auto /dev/vdb |& systemd-cat")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix
new file mode 100644
index 000000000000..617c003484b9
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-keyfile.nix
@@ -0,0 +1,56 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: let
+
+  keyfile = pkgs.writeText "luks-keyfile" ''
+    MIGHAoGBAJ4rGTSo/ldyjQypd0kuS7k2OSsmQYzMH6TNj3nQ/vIUjDn7fqa3slt2
+    gV6EK3TmTbGc4tzC1v4SWx2m+2Bjdtn4Fs4wiBwn1lbRdC6i5ZYCqasTWIntWn+6
+    FllUkMD5oqjOR/YcboxG8Z3B5sJuvTP9llsF+gnuveWih9dpbBr7AgEC
+  '';
+
+in {
+  name = "systemd-initrd-luks-keyfile";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = true;
+      # Necessary to boot off the encrypted disk because it requires a init script coming from the Nix store
+      mountHostNixStore = true;
+      useEFIBoot = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+    boot.initrd.systemd = {
+      enable = true;
+      emergencyAccess = true;
+    };
+
+    specialisation.boot-luks.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          keyFile = "/etc/cryptroot.key";
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      virtualisation.fileSystems."/".autoFormat = true;
+      boot.initrd.secrets."/etc/cryptroot.key" = keyfile;
+    };
+  };
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("cryptsetup luksFormat -q --iter-time=1 -d ${keyfile} /dev/vdb")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-password.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-password.nix
new file mode 100644
index 000000000000..66b5022d87fd
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-password.nix
@@ -0,0 +1,56 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-luks-password";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 512 ];
+      useBootLoader = true;
+      # Booting off the encrypted disk requires an available init script
+      mountHostNixStore = true;
+      useEFIBoot = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+    boot.initrd.systemd = {
+      enable = true;
+      emergencyAccess = true;
+    };
+
+    specialisation.boot-luks.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        # We have two disks and only type one password - key reuse is in place
+        cryptroot.device = "/dev/vdb";
+        cryptroot2.device = "/dev/vdc";
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      virtualisation.fileSystems."/".autoFormat = true;
+      # test mounting device unlocked in initrd after switching root
+      virtualisation.fileSystems."/cryptroot2".device = "/dev/mapper/cryptroot2";
+    };
+  };
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
+    machine.succeed("echo -n supersecret | cryptsetup luksOpen   -q               /dev/vdc cryptroot2")
+    machine.succeed("mkfs.ext4 /dev/mapper/cryptroot2")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_console_text("Please enter passphrase for disk cryptroot")
+    machine.send_console("supersecret\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount"), "/dev/mapper/cryptroot do not appear in mountpoints list"
+    assert "/dev/mapper/cryptroot2 on /cryptroot2 type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-tpm2.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-tpm2.nix
new file mode 100644
index 000000000000..e292acfd1c5f
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-tpm2.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-luks-tpm2";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 ];
+      useBootLoader = true;
+      # Booting off the TPM2-encrypted device requires an available init script
+      mountHostNixStore = true;
+      useEFIBoot = true;
+      tpm.enable = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.initrd.availableKernelModules = [ "tpm_tis" ];
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+    boot.initrd.systemd = {
+      enable = true;
+    };
+
+    specialisation.boot-luks.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        cryptroot = {
+          device = "/dev/vdb";
+          crypttabExtraOpts = [ "tpm2-device=auto" ];
+        };
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      virtualisation.fileSystems."/".autoFormat = true;
+    };
+  };
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --tpm2-pcrs= --tpm2-device=auto /dev/vdb |& systemd-cat")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.wait_for_unit("multi-user.target")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-luks-unl0kr.nix b/nixpkgs/nixos/tests/systemd-initrd-luks-unl0kr.nix
new file mode 100644
index 000000000000..0658a098cfa2
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-luks-unl0kr.nix
@@ -0,0 +1,75 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: let
+  passphrase = "secret";
+in {
+  name = "systemd-initrd-luks-unl0kr";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tomfitzhenry ];
+  };
+
+  enableOCR = true;
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation = {
+      emptyDiskImages = [ 512 512 ];
+      useBootLoader = true;
+      mountHostNixStore = true;
+      useEFIBoot = true;
+      qemu.options = [
+        "-vga virtio"
+      ];
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.initrd.availableKernelModules = [
+      "evdev" # for entering pw
+      "bochs"
+    ];
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+    boot.initrd = {
+      systemd = {
+        enable = true;
+        emergencyAccess = true;
+      };
+      unl0kr.enable = true;
+    };
+
+    specialisation.boot-luks.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        # We have two disks and only type one password - key reuse is in place
+        cryptroot.device = "/dev/vdb";
+        cryptroot2.device = "/dev/vdc";
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      virtualisation.fileSystems."/".autoFormat = true;
+      # test mounting device unlocked in initrd after switching root
+      virtualisation.fileSystems."/cryptroot2".device = "/dev/mapper/cryptroot2";
+    };
+  };
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n ${passphrase} | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("echo -n ${passphrase} | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
+    machine.succeed("echo -n ${passphrase} | cryptsetup luksOpen   -q               /dev/vdc cryptroot2")
+    machine.succeed("mkfs.ext4 /dev/mapper/cryptroot2")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_text("Password required for booting")
+    machine.screenshot("prompt")
+    machine.send_chars("${passphrase}")
+    machine.screenshot("pw")
+    machine.send_chars("\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount"), "/dev/mapper/cryptroot do not appear in mountpoints list"
+    assert "/dev/mapper/cryptroot2 on /cryptroot2 type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-modprobe.nix b/nixpkgs/nixos/tests/systemd-initrd-modprobe.nix
new file mode 100644
index 000000000000..0f93492176b4
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-modprobe.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-modprobe";
+
+  nodes.machine = { pkgs, ... }: {
+    testing.initrdBackdoor = true;
+    boot.initrd.systemd.enable = true;
+    boot.initrd.kernelModules = [ "loop" ]; # Load module in initrd.
+    boot.extraModprobeConfig = ''
+      options loop max_loop=42
+    '';
+  };
+
+  testScript = ''
+    machine.wait_for_unit("initrd.target")
+    max_loop = machine.succeed("cat /sys/module/loop/parameters/max_loop")
+    assert int(max_loop) == 42, "Parameter should be respected for initrd kernel modules"
+
+    # Make sure it sticks in stage 2
+    machine.switch_root()
+    machine.wait_for_unit("multi-user.target")
+    max_loop = machine.succeed("cat /sys/module/loop/parameters/max_loop")
+    assert int(max_loop) == 42, "Parameter should be respected for initrd kernel modules"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-networkd-ssh.nix b/nixpkgs/nixos/tests/systemd-initrd-networkd-ssh.nix
new file mode 100644
index 000000000000..d4c168f40e29
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-networkd-ssh.nix
@@ -0,0 +1,60 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-initrd-network-ssh";
+  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+
+  nodes = {
+    server = { config, pkgs, ... }: {
+      testing.initrdBackdoor = true;
+      boot.initrd.systemd.enable = true;
+      boot.initrd.systemd.contents."/etc/msg".text = "foo";
+      boot.initrd.network = {
+        enable = true;
+        ssh = {
+          enable = true;
+          authorizedKeys = [ (lib.readFile ./initrd-network-ssh/id_ed25519.pub) ];
+          port = 22;
+          hostKeys = [ ./initrd-network-ssh/ssh_host_ed25519_key ];
+        };
+      };
+    };
+
+    client = { config, ... }: {
+      environment.etc = {
+        knownHosts = {
+          text = lib.concatStrings [
+            "server,"
+            "${
+              toString (lib.head (lib.splitString " " (toString
+                (lib.elemAt (lib.splitString "\n" config.networking.extraHosts) 2))))
+            } "
+            "${lib.readFile ./initrd-network-ssh/ssh_host_ed25519_key.pub}"
+          ];
+        };
+        sshKey = {
+          source = ./initrd-network-ssh/id_ed25519;
+          mode = "0600";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    def ssh_is_up(_) -> bool:
+        status, _ = client.execute("nc -z server 22")
+        return status == 0
+
+    client.wait_for_unit("network.target")
+    with client.nested("waiting for SSH server to come up"):
+        retry(ssh_is_up)
+
+    msg = client.succeed(
+        "ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'cat /etc/msg'"
+    )
+    assert "foo" in msg
+
+    server.switch_root()
+    server.wait_for_unit("multi-user.target")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-networkd.nix b/nixpkgs/nixos/tests/systemd-initrd-networkd.nix
new file mode 100644
index 000000000000..691f4300d7a2
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-networkd.nix
@@ -0,0 +1,94 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (lib.maintainers) elvishjerricco;
+
+  common = {
+    boot.initrd.systemd = {
+      enable = true;
+      network.wait-online.timeout = 10;
+      network.wait-online.anyInterface = true;
+      targets.network-online.requiredBy = [ "initrd.target" ];
+      services.systemd-networkd-wait-online.requiredBy =
+        [ "network-online.target" ];
+      initrdBin = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+    };
+    testing.initrdBackdoor = true;
+    boot.initrd.network.enable = true;
+  };
+
+  mkFlushTest = flush: script: makeTest {
+    name = "systemd-initrd-network-${lib.optionalString (!flush) "no-"}flush";
+    meta.maintainers = [ elvishjerricco ];
+
+    nodes.machine = {
+      imports = [ common ];
+
+      boot.initrd.network.flushBeforeStage2 = flush;
+      systemd.services.check-flush = {
+        requiredBy = ["multi-user.target"];
+        before = [ "network-pre.target" "multi-user.target" "shutdown.target" ];
+        conflicts = [ "shutdown.target" ];
+        wants = ["network-pre.target"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig.Type = "oneshot";
+        path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+        inherit script;
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("network-online.target")
+      machine.succeed(
+          "ip addr | grep 10.0.2.15",
+          "ping -c1 10.0.2.2",
+      )
+      machine.switch_root()
+
+      machine.wait_for_unit("multi-user.target")
+    '';
+  };
+
+in {
+  basic = makeTest {
+    name = "systemd-initrd-network";
+    meta.maintainers = [ elvishjerricco ];
+
+    nodes.machine = common;
+
+    testScript = ''
+      machine.wait_for_unit("network-online.target")
+      machine.succeed(
+          "ip addr | grep 10.0.2.15",
+          "ping -c1 10.0.2.2",
+      )
+      machine.switch_root()
+
+      # Make sure the systemd-network user was set correctly in initrd
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("[ $(stat -c '%U,%G' /run/systemd/netif/links) = systemd-network,systemd-network ]")
+      machine.succeed("ip addr show >&2")
+      machine.succeed("ip route show >&2")
+    '';
+  };
+
+  doFlush = mkFlushTest true ''
+    if ip addr | grep 10.0.2.15; then
+      echo "Network configuration survived switch-root; flushBeforeStage2 failed"
+      exit 1
+    fi
+  '';
+
+  dontFlush = mkFlushTest false ''
+    if ! (ip addr | grep 10.0.2.15); then
+      echo "Network configuration didn't survive switch-root"
+      exit 1
+    fi
+  '';
+}
diff --git a/nixpkgs/nixos/tests/systemd-initrd-simple.nix b/nixpkgs/nixos/tests/systemd-initrd-simple.nix
new file mode 100644
index 000000000000..2b7283a82193
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-simple.nix
@@ -0,0 +1,48 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-simple";
+
+  nodes.machine = { pkgs, ... }: {
+    testing.initrdBackdoor = true;
+    boot.initrd.systemd.enable = true;
+    virtualisation.fileSystems."/".autoResize = true;
+  };
+
+  testScript = ''
+    import subprocess
+
+    with subtest("testing initrd backdoor"):
+        machine.wait_for_unit("initrd.target")
+        machine.succeed("systemctl status initrd-fs.target")
+        machine.switch_root()
+
+    with subtest("handover to stage-2 systemd works"):
+        machine.wait_for_unit("multi-user.target")
+        machine.succeed("systemd-analyze | grep -q '(initrd)'")  # direct handover
+        machine.succeed("touch /testfile")  # / is writable
+        machine.fail("touch /nix/store/testfile")  # /nix/store is not writable
+        # Special filesystems are mounted by systemd
+        machine.succeed("[ -e /run/booted-system ]") # /run
+        machine.succeed("[ -e /sys/class ]") # /sys
+        machine.succeed("[ -e /dev/null ]") # /dev
+        machine.succeed("[ -e /proc/1 ]") # /proc
+        # stage-2-init mounted more special filesystems
+        machine.succeed("[ -e /dev/shm ]") # /dev/shm
+        machine.succeed("[ -e /dev/pts/ptmx ]") # /dev/pts
+        machine.succeed("[ -e /run/keys ]") # /run/keys
+
+    with subtest("groups work"):
+        machine.fail("journalctl -b 0 | grep 'systemd-udevd.*Unknown group.*ignoring'")
+
+    with subtest("growfs works"):
+        oldAvail = machine.succeed("df --output=avail / | sed 1d")
+        machine.shutdown()
+
+        subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
+
+        machine.start()
+        machine.switch_root()
+        newAvail = machine.succeed("df --output=avail / | sed 1d")
+
+        assert int(oldAvail) < int(newAvail), "File system did not grow"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-swraid.nix b/nixpkgs/nixos/tests/systemd-initrd-swraid.nix
new file mode 100644
index 000000000000..d00e67b5705a
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-swraid.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-swraid";
+
+  nodes.machine = { pkgs, ... }: {
+    # Use systemd-boot
+    virtualisation = {
+      emptyDiskImages = [ 512 512 ];
+      useBootLoader = true;
+      # Booting off the RAID requires an available init script
+      mountHostNixStore = true;
+      useEFIBoot = true;
+    };
+    boot.loader.systemd-boot.enable = true;
+    boot.loader.efi.canTouchEfiVariables = true;
+
+    environment.systemPackages = with pkgs; [ mdadm e2fsprogs ]; # for mdadm and mkfs.ext4
+    boot.swraid = {
+      enable = true;
+      mdadmConf = ''
+        ARRAY /dev/md0 devices=/dev/vdb,/dev/vdc
+      '';
+    };
+    environment.etc."mdadm.conf".text = ''
+      MAILADDR test@example.com
+    '';
+    boot.initrd = {
+      systemd = {
+        enable = true;
+        emergencyAccess = true;
+      };
+      kernelModules = [ "raid0" ];
+    };
+
+    specialisation.boot-swraid.configuration.virtualisation.rootDevice = "/dev/disk/by-label/testraid";
+    # This protects against a regression. We do not have to switch to it.
+    # It's sufficient to trigger its evaluation.
+    specialisation.build-old-initrd.configuration.boot.initrd.systemd.enable = lib.mkForce false;
+  };
+
+  testScript = ''
+    # Create RAID
+    machine.succeed("mdadm --create --force /dev/md0 -n 2 --level=raid1 /dev/vdb /dev/vdc --metadata=0.90")
+    machine.succeed("mkfs.ext4 -L testraid /dev/md0")
+    machine.succeed("mkdir -p /mnt && mount /dev/md0 /mnt && echo hello > /mnt/test && umount /mnt")
+
+    # Boot from the RAID
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-swraid.conf")
+    machine.succeed("sync")
+    machine.crash()
+    machine.wait_for_unit("multi-user.target")
+
+    # Ensure we have successfully booted from the RAID
+    assert "(initrd)" in machine.succeed("systemd-analyze")  # booted with systemd in stage 1
+    assert "/dev/md0 on / type ext4" in machine.succeed("mount")
+    assert "hello" in machine.succeed("cat /test")
+    assert "md0" in machine.succeed("cat /proc/mdstat")
+
+    expected_config = """MAILADDR test@example.com
+
+    ARRAY /dev/md0 devices=/dev/vdb,/dev/vdc
+    """
+    got_config = machine.execute("cat /etc/mdadm.conf")[1]
+    assert expected_config == got_config, repr((expected_config, got_config))
+    machine.wait_for_unit("mdmonitor.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-vconsole.nix b/nixpkgs/nixos/tests/systemd-initrd-vconsole.nix
new file mode 100644
index 000000000000..d4c2a57680c1
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-vconsole.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "systemd-initrd-vconsole";
+
+  nodes.machine = { pkgs, ... }: {
+    boot.kernelParams = lib.mkAfter [ "rd.systemd.unit=rescue.target" "loglevel=3" "udev.log_level=3" "systemd.log_level=warning" ];
+
+    boot.initrd.systemd = {
+      enable = true;
+      emergencyAccess = true;
+    };
+
+    console = {
+      earlySetup = true;
+      keyMap = "colemak";
+    };
+  };
+
+  testScript = ''
+    # Boot into rescue shell in initrd
+    machine.start()
+    machine.wait_for_console_text("Press Enter for maintenance")
+    machine.send_console("\n")
+
+    # Wait for shell to become ready
+    for _ in range(300):
+      machine.send_console("printf '%s to receive commands:\\n' Ready\n")
+      try:
+        machine.wait_for_console_text("Ready to receive commands:", timeout=1)
+        break
+      except Exception:
+        continue
+    else:
+      raise RuntimeError("Rescue shell never became ready")
+
+    # Check keymap
+    machine.send_console("(printf '%s to receive text:\\n' Ready && read text && echo \"$text\") </dev/tty1\n")
+    machine.wait_for_console_text("Ready to receive text:")
+    for key in "asdfjkl;\n":
+      machine.send_key(key)
+    machine.wait_for_console_text("arstneio")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-initrd-vlan.nix b/nixpkgs/nixos/tests/systemd-initrd-vlan.nix
new file mode 100644
index 000000000000..5060163a047d
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-initrd-vlan.nix
@@ -0,0 +1,59 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-initrd-vlan";
+  meta.maintainers = [ lib.maintainers.majiir ];
+
+  # Tests VLAN interface configuration in systemd-initrd.
+  #
+  # Two nodes are configured for a tagged VLAN. (Note that they also still have
+  # their ordinary eth0 and eth1 interfaces, which are not VLAN-tagged.)
+  #
+  # The 'server' node waits forever in initrd (stage 1) with networking
+  # enabled. The 'client' node pings it to test network connectivity.
+
+  nodes = let
+    network = id: {
+      networking = {
+        vlans."eth1.10" = {
+          id = 10;
+          interface = "eth1";
+        };
+        interfaces."eth1.10" = {
+          ipv4.addresses = [{
+            address = "192.168.10.${id}";
+            prefixLength = 24;
+          }];
+        };
+      };
+    };
+  in {
+    # Node that will use initrd networking.
+    server = network "1" // {
+      boot.initrd.systemd.enable = true;
+      boot.initrd.network.enable = true;
+      boot.initrd.systemd.services.boot-blocker = {
+        before = [ "initrd.target" ];
+        wantedBy = [ "initrd.target" ];
+        script = "sleep infinity";
+        serviceConfig.Type = "oneshot";
+      };
+    };
+
+    # Node that will ping the server.
+    client = network "2";
+  };
+
+  testScript = ''
+    start_all()
+    client.wait_for_unit("network.target")
+
+    # Wait for the regular (untagged) interface to be up.
+    def server_is_up(_) -> bool:
+        status, _ = client.execute("ping -n -c 1 server >&2")
+        return status == 0
+    with client.nested("waiting for server to come up"):
+        retry(server_is_up)
+
+    # Try to ping the (tagged) VLAN interface.
+    client.succeed("ping -n -w 10 -c 1 192.168.10.1 >&2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-journal-gateway.nix b/nixpkgs/nixos/tests/systemd-journal-gateway.nix
new file mode 100644
index 000000000000..1d20943f2388
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-journal-gateway.nix
@@ -0,0 +1,90 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "systemd-journal-gateway";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ minijackson raitobezarius ];
+  };
+
+  # Named client for coherence with the systemd-journal-upload test, and for
+  # certificate validation
+  nodes.client = {
+    services.journald.gateway = {
+      enable = true;
+      cert = "/run/secrets/client/cert.pem";
+      key = "/run/secrets/client/key.pem";
+      trust = "/run/secrets/ca.cert.pem";
+    };
+  };
+
+  testScript = ''
+    import json
+    import subprocess
+    import tempfile
+
+    tmpdir_o = tempfile.TemporaryDirectory()
+    tmpdir = tmpdir_o.name
+
+    def generate_pems(domain: str):
+      subprocess.run(
+        [
+          "${pkgs.minica}/bin/minica",
+          "--ca-key=ca.key.pem",
+          "--ca-cert=ca.cert.pem",
+          f"--domains={domain}",
+        ],
+        cwd=str(tmpdir),
+      )
+
+    with subtest("Creating keys and certificates"):
+      generate_pems("server")
+      generate_pems("client")
+
+    client.wait_for_unit("multi-user.target")
+
+    def copy_pem(file: str):
+      machine.copy_from_host(source=f"{tmpdir}/{file}", target=f"/run/secrets/{file}")
+      machine.succeed(f"chmod 644 /run/secrets/{file}")
+
+    with subtest("Copying keys and certificates"):
+      machine.succeed("mkdir -p /run/secrets/{client,server}")
+      copy_pem("server/cert.pem")
+      copy_pem("server/key.pem")
+      copy_pem("client/cert.pem")
+      copy_pem("client/key.pem")
+      copy_pem("ca.cert.pem")
+
+    client.wait_for_unit("multi-user.target")
+
+    curl = '${pkgs.curl}/bin/curl'
+    accept_json = '--header "Accept: application/json"'
+    cacert = '--cacert /run/secrets/ca.cert.pem'
+    cert = '--cert /run/secrets/server/cert.pem'
+    key = '--key /run/secrets/server/key.pem'
+    base_url = 'https://client:19531'
+
+    curl_cli = f"{curl} {accept_json} {cacert} {cert} {key} --fail"
+
+    machine_info = client.succeed(f"{curl_cli} {base_url}/machine")
+    assert json.loads(machine_info)["hostname"] == "client", "wrong machine name"
+
+    # The HTTP request should have started the gateway service, triggered by
+    # the .socket unit
+    client.wait_for_unit("systemd-journal-gatewayd.service")
+
+    identifier = "nixos-test"
+    message = "Hello from NixOS test infrastructure"
+
+    client.succeed(f"systemd-cat --identifier={identifier} <<< '{message}'")
+
+    # max-time is a workaround against a bug in systemd-journal-gatewayd where
+    # if TLS is enabled, the connection is never closed. Since it will timeout,
+    # we ignore the return code.
+    entries = client.succeed(
+        f"{curl_cli} --max-time 5 {base_url}/entries?SYSLOG_IDENTIFIER={identifier} || true"
+    )
+
+    # Number of entries should be only 1
+    added_entry = json.loads(entries)
+    assert added_entry["SYSLOG_IDENTIFIER"] == identifier and added_entry["MESSAGE"] == message, "journal entry does not correspond"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-journal-upload.nix b/nixpkgs/nixos/tests/systemd-journal-upload.nix
new file mode 100644
index 000000000000..0cbde379aee9
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-journal-upload.nix
@@ -0,0 +1,101 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "systemd-journal-upload";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ minijackson raitobezarius ];
+  };
+
+  nodes.server = { nodes, ... }: {
+    services.journald.remote = {
+      enable = true;
+      listen = "http";
+      settings.Remote = {
+        ServerCertificateFile = "/run/secrets/sever.cert.pem";
+        ServerKeyFile = "/run/secrets/sever.key.pem";
+        TrustedCertificateFile = "/run/secrets/ca.cert.pem";
+        Seal = true;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = [ nodes.server.services.journald.remote.port ];
+  };
+
+  nodes.client = { lib, nodes, ... }: {
+    services.journald.upload = {
+      enable = true;
+      settings.Upload = {
+        URL = "http://server:${toString nodes.server.services.journald.remote.port}";
+        ServerCertificateFile = "/run/secrets/client.cert.pem";
+        ServerKeyFile = "/run/secrets/client.key.pem";
+        TrustedCertificateFile = "/run/secrets/ca.cert.pem";
+      };
+    };
+
+    # Wait for the PEMs to arrive
+    systemd.services.systemd-journal-upload.wantedBy = lib.mkForce [];
+    systemd.paths.systemd-journal-upload = {
+      wantedBy = [ "default.target" ];
+      # This file must be copied last
+      pathConfig.PathExists = [ "/run/secrets/ca.cert.pem" ];
+    };
+  };
+
+  testScript = ''
+    import subprocess
+    import tempfile
+
+    tmpdir_o = tempfile.TemporaryDirectory()
+    tmpdir = tmpdir_o.name
+
+    def generate_pems(domain: str):
+      subprocess.run(
+        [
+          "${pkgs.minica}/bin/minica",
+          "--ca-key=ca.key.pem",
+          "--ca-cert=ca.cert.pem",
+          f"--domains={domain}",
+        ],
+        cwd=str(tmpdir),
+      )
+
+    with subtest("Creating keys and certificates"):
+      generate_pems("server")
+      generate_pems("client")
+
+    server.wait_for_unit("multi-user.target")
+    client.wait_for_unit("multi-user.target")
+
+    def copy_pems(machine: Machine, domain: str):
+      machine.succeed("mkdir /run/secrets")
+      machine.copy_from_host(
+        source=f"{tmpdir}/{domain}/cert.pem",
+        target=f"/run/secrets/{domain}.cert.pem",
+      )
+      machine.copy_from_host(
+        source=f"{tmpdir}/{domain}/key.pem",
+        target=f"/run/secrets/{domain}.key.pem",
+      )
+      # Should be last
+      machine.copy_from_host(
+        source=f"{tmpdir}/ca.cert.pem",
+        target="/run/secrets/ca.cert.pem",
+      )
+
+    with subtest("Copying keys and certificates"):
+      copy_pems(server, "server")
+      copy_pems(client, "client")
+
+    client.wait_for_unit("systemd-journal-upload.service")
+    # The journal upload should have started the remote service, triggered by
+    # the .socket unit
+    server.wait_for_unit("systemd-journal-remote.service")
+
+    identifier = "nixos-test"
+    message = "Hello from NixOS test infrastructure"
+
+    client.succeed(f"systemd-cat --identifier={identifier} <<< '{message}'")
+    server.wait_until_succeeds(
+      f"journalctl --file /var/log/journal/remote/remote-*.journal --identifier={identifier} | grep -F '{message}'"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-journal.nix b/nixpkgs/nixos/tests/systemd-journal.nix
new file mode 100644
index 000000000000..ad60c0f547a4
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-journal.nix
@@ -0,0 +1,16 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "systemd-journal";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lewo ];
+  };
+
+  nodes.machine = { };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    machine.succeed("journalctl --grep=systemd")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-lock-handler.nix b/nixpkgs/nixos/tests/systemd-lock-handler.nix
new file mode 100644
index 000000000000..d6fb8f545900
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-lock-handler.nix
@@ -0,0 +1,56 @@
+{ lib, ... }: {
+  name = "systemd-lock-handler";
+
+  meta.maintainers = with lib.maintainers; [ liff ];
+
+  enableOCR = true;
+
+  nodes.machine = { config, pkgs, lib, ... }:
+    let
+      touch = "${lib.getBin pkgs.coreutils}/bin/touch";
+    in
+    {
+      imports = [ common/wayland-cage.nix ];
+
+      services.systemd-lock-handler.enable = true;
+
+      systemd.user.services = {
+        test-lock = {
+          partOf = [ "lock.target" ];
+          onSuccess = [ "unlock.target" ];
+          before = [ "lock.target" ];
+          wantedBy = [ "lock.target" ];
+          serviceConfig.ExecStart = "${touch} /tmp/lock.target.activated";
+        };
+        test-unlock = {
+          partOf = [ "unlock.target" ];
+          after = [ "unlock.target" ];
+          wantedBy = [ "unlock.target" ];
+          serviceConfig.ExecStart = "${touch} /tmp/unlock.target.activated";
+        };
+        test-sleep = {
+          partOf = [ "sleep.target" ];
+          before = [ "sleep.target" ];
+          wantedBy = [ "sleep.target" ];
+          serviceConfig.ExecStart = "${touch} /tmp/sleep.target.activated";
+        };
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit('graphical.target')
+    machine.wait_for_text('alice@machine')
+
+    machine.send_chars('loginctl lock-session\n')
+    machine.wait_for_file('/tmp/lock.target.activated')
+    machine.wait_for_file('/tmp/unlock.target.activated')
+
+    machine.send_chars('systemctl suspend\n')
+    # wait_for_file won’t complete before the machine is asleep,
+    # so we’ll watch the log instead.
+    machine.wait_for_console_text('Started test-sleep.service.')
+
+    # The VM is asleep, regular shutdown won’t work.
+    machine.crash()
+  '';
+}
diff --git a/nixpkgs/nixos/tests/systemd-machinectl.nix b/nixpkgs/nixos/tests/systemd-machinectl.nix
new file mode 100644
index 000000000000..b8ed0c33e8e4
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-machinectl.nix
@@ -0,0 +1,114 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+
+    container = {
+      # We re-use the NixOS container option ...
+      boot.isContainer = true;
+      # ... and revert unwanted defaults
+      networking.useHostResolvConf = false;
+
+      # use networkd to obtain systemd network setup
+      networking.useNetworkd = true;
+      networking.useDHCP = false;
+
+      # systemd-nspawn expects /sbin/init
+      boot.loader.initScript.enable = true;
+
+      imports = [ ../modules/profiles/minimal.nix ];
+    };
+
+    containerSystem = (import ../lib/eval-config.nix {
+      inherit (pkgs) system;
+      modules = [ container ];
+    }).config.system.build.toplevel;
+
+    containerName = "container";
+    containerRoot = "/var/lib/machines/${containerName}";
+
+  in
+  {
+    name = "systemd-machinectl";
+
+    nodes.machine = { lib, ... }: {
+      # use networkd to obtain systemd network setup
+      networking.useNetworkd = true;
+      networking.useDHCP = false;
+
+      # do not try to access cache.nixos.org
+      nix.settings.substituters = lib.mkForce [ ];
+
+      # auto-start container
+      systemd.targets.machines.wants = [ "systemd-nspawn@${containerName}.service" ];
+
+      virtualisation.additionalPaths = [ containerSystem ];
+
+      # not needed, but we want to test the nspawn file generation
+      systemd.nspawn.${containerName} = { };
+
+      systemd.services."systemd-nspawn@${containerName}" = {
+        serviceConfig.Environment = [
+          # Disable tmpfs for /tmp
+          "SYSTEMD_NSPAWN_TMPFS_TMP=0"
+        ];
+        overrideStrategy = "asDropin";
+      };
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("default.target");
+
+      # Install container
+      machine.succeed("mkdir -p ${containerRoot}");
+      # Workaround for nixos-install
+      machine.succeed("chmod o+rx /var/lib/machines");
+      machine.succeed("nixos-install --root ${containerRoot} --system ${containerSystem} --no-channel-copy --no-root-passwd");
+
+      # Allow systemd-nspawn to apply user namespace on immutable files
+      machine.succeed("chattr -i ${containerRoot}/var/empty");
+
+      # Test machinectl start
+      machine.succeed("machinectl start ${containerName}");
+      machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
+
+      # Test nss_mymachines without nscd
+      machine.succeed('LD_LIBRARY_PATH="/run/current-system/sw/lib" getent -s hosts:mymachines hosts ${containerName}');
+
+      # Test nss_mymachines via nscd
+      machine.succeed("getent hosts ${containerName}");
+
+      # Test systemd-nspawn network configuration
+      machine.succeed("ping -n -c 1 ${containerName}");
+
+      # Test systemd-nspawn uses a user namespace
+      machine.succeed("test $(machinectl status ${containerName} | grep 'UID Shift: ' | wc -l) = 1")
+
+      # Test systemd-nspawn reboot
+      machine.succeed("machinectl shell ${containerName} /run/current-system/sw/bin/reboot");
+      machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
+
+      # Test machinectl reboot
+      machine.succeed("machinectl reboot ${containerName}");
+      machine.wait_until_succeeds("systemctl -M ${containerName} is-active default.target");
+
+      # Restart machine
+      machine.shutdown()
+      machine.start()
+      machine.wait_for_unit("default.target");
+
+      # Test auto-start
+      machine.succeed("machinectl show ${containerName}")
+
+      # Test machinectl stop
+      machine.succeed("machinectl stop ${containerName}");
+      machine.wait_until_succeeds("test $(systemctl is-active systemd-nspawn@${containerName}) = inactive");
+
+      # Test tmpfs for /tmp
+      machine.fail("mountpoint /tmp");
+
+      # Show to to delete the container
+      machine.succeed("chattr -i ${containerRoot}/var/empty");
+      machine.succeed("rm -rf ${containerRoot}");
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/systemd-misc.nix b/nixpkgs/nixos/tests/systemd-misc.nix
new file mode 100644
index 000000000000..0ddd51100463
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-misc.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  exampleScript = pkgs.writeTextFile {
+    name = "example.sh";
+    text = ''
+      #! ${pkgs.runtimeShell} -e
+
+      while true; do
+          echo "Example script running" >&2
+          ${pkgs.coreutils}/bin/sleep 1
+      done
+    '';
+    executable = true;
+  };
+
+  unitFile = pkgs.writeTextFile {
+    name = "example.service";
+    text = ''
+      [Unit]
+      Description=Example systemd service unit file
+
+      [Service]
+      ExecStart=${exampleScript}
+
+      [Install]
+      WantedBy=multi-user.target
+    '';
+  };
+in
+{
+  name = "systemd-misc";
+
+  nodes.machine = { pkgs, lib, ... }: {
+    boot.extraSystemdUnitPaths = [ "/etc/systemd-rw/system" ];
+
+    users.users.limited = {
+      isNormalUser = true;
+      uid = 1000;
+    };
+
+    systemd.units."user-1000.slice.d/limits.conf" = {
+      text = ''
+        [Slice]
+        TasksAccounting=yes
+        TasksMax=100
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("mkdir -p /etc/systemd-rw/system")
+    machine.succeed(
+        "cp ${unitFile} /etc/systemd-rw/system/example.service"
+    )
+    machine.succeed("systemctl start example.service")
+    machine.succeed("systemctl status example.service | grep 'Active: active'")
+
+    machine.succeed("systemctl show --property TasksMax --value user-1000.slice | grep 100")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix b/nixpkgs/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix
new file mode 100644
index 000000000000..f6d5411aa5ca
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix
@@ -0,0 +1,81 @@
+# In contrast to systemd-networkd-dhcpserver, this test configures
+# the router with a static DHCP lease for the client's MAC address.
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-networkd-dhcpserver-static-leases";
+  meta = with lib.maintainers; {
+    maintainers = [ veehaitch ];
+  };
+  nodes = {
+    router = {
+      virtualisation.vlans = [ 1 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+      systemd.network = {
+        networks = {
+          # systemd-networkd will load the first network unit file
+          # that matches, ordered lexiographically by filename.
+          # /etc/systemd/network/{40-eth1,99-main}.network already
+          # exists. This network unit must be loaded for the test,
+          # however, hence why this network is named such.
+          "01-eth1" = {
+            name = "eth1";
+            networkConfig = {
+              DHCPServer = true;
+              Address = "10.0.0.1/24";
+            };
+            dhcpServerStaticLeases = [{
+              dhcpServerStaticLeaseConfig = {
+                MACAddress = "02:de:ad:be:ef:01";
+                Address = "10.0.0.10";
+              };
+            }];
+          };
+        };
+      };
+    };
+
+    client = {
+      virtualisation.vlans = [ 1 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1 = {
+          useDHCP = true;
+          macAddress = "02:de:ad:be:ef:01";
+        };
+      };
+
+      # This setting is important to have the router assign the
+      # configured lease based on the client's MAC address. Also see:
+      # https://github.com/systemd/systemd/issues/21368#issuecomment-982193546
+      systemd.network.networks."40-eth1".dhcpV4Config.ClientIdentifier = "mac";
+    };
+  };
+  testScript = ''
+    start_all()
+
+    with subtest("check router network configuration"):
+      router.wait_for_unit("systemd-networkd-wait-online.service")
+      eth1_status = router.succeed("networkctl status eth1")
+      assert "Network File: /etc/systemd/network/01-eth1.network" in eth1_status, \
+        "The router interface eth1 is not using the expected network file"
+      assert "10.0.0.1" in eth1_status, "Did not find expected router IPv4"
+
+    with subtest("check client network configuration"):
+      client.wait_for_unit("systemd-networkd-wait-online.service")
+      eth1_status = client.succeed("networkctl status eth1")
+      assert "Network File: /etc/systemd/network/40-eth1.network" in eth1_status, \
+        "The client interface eth1 is not using the expected network file"
+      assert "10.0.0.10" in eth1_status, "Did not find expected client IPv4"
+
+    with subtest("router and client can reach each other"):
+      client.wait_until_succeeds("ping -c 5 10.0.0.1")
+      router.wait_until_succeeds("ping -c 5 10.0.0.10")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-networkd-dhcpserver.nix b/nixpkgs/nixos/tests/systemd-networkd-dhcpserver.nix
new file mode 100644
index 000000000000..665d8b5a0529
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-networkd-dhcpserver.nix
@@ -0,0 +1,112 @@
+# This test predominantly tests systemd-networkd DHCP server, by
+# setting up a DHCP server and client, and ensuring they are mutually
+# reachable via the DHCP allocated address.
+# Two DHCP servers are set up on bridge VLANs, testing to make sure that
+# bridge VLAN settings are correctly applied.
+#
+# br0 ----untagged---v
+#                    +---PVID 1+VLAN 2---[bridge]---PVID 2---eth1
+# vlan2 ---VLAN 2----^
+import ./make-test-python.nix ({pkgs, ...}: {
+  name = "systemd-networkd-dhcpserver";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+  nodes = {
+    router = { config, pkgs, ... }: {
+      virtualisation.vlans = [ 1 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+      systemd.network = {
+        netdevs = {
+          br0 = {
+            enable = true;
+            netdevConfig = {
+              Name = "br0";
+              Kind = "bridge";
+            };
+            extraConfig = ''
+              [Bridge]
+              VLANFiltering=yes
+              DefaultPVID=none
+            '';
+          };
+          vlan2 = {
+            enable = true;
+            netdevConfig = {
+              Name = "vlan2";
+              Kind = "vlan";
+            };
+            vlanConfig.Id = 2;
+          };
+        };
+        networks = {
+          # systemd-networkd will load the first network unit file
+          # that matches, ordered lexiographically by filename.
+          # /etc/systemd/network/{40-eth1,99-main}.network already
+          # exists. This network unit must be loaded for the test,
+          # however, hence why this network is named such.
+          "01-eth1" = {
+            name = "eth1";
+            networkConfig.Bridge = "br0";
+            bridgeVLANs = [
+              { bridgeVLANConfig = { PVID = 2; EgressUntagged = 2; }; }
+            ];
+          };
+          "02-br0" = {
+            name = "br0";
+            networkConfig = {
+              DHCPServer = true;
+              Address = "10.0.0.1/24";
+              VLAN = ["vlan2"];
+            };
+            dhcpServerConfig = {
+              PoolOffset = 100;
+              PoolSize = 1;
+            };
+            bridgeVLANs = [
+              { bridgeVLANConfig = { PVID = 1; EgressUntagged = 1; }; }
+              { bridgeVLANConfig = { VLAN = 2; }; }
+            ];
+          };
+          "02-vlan2" = {
+            name = "vlan2";
+            networkConfig = {
+              DHCPServer = true;
+              Address = "10.0.2.1/24";
+            };
+            dhcpServerConfig = {
+              PoolOffset = 100;
+              PoolSize = 1;
+            };
+          };
+        };
+      };
+    };
+
+    client = { config, pkgs, ... }: {
+      virtualisation.vlans = [ 1 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1.useDHCP = true;
+      };
+    };
+  };
+  testScript = { ... }: ''
+    start_all()
+
+    router.systemctl("start network-online.target")
+    client.systemctl("start network-online.target")
+    router.wait_for_unit("systemd-networkd-wait-online.service")
+    client.wait_for_unit("systemd-networkd-wait-online.service")
+    client.wait_until_succeeds("ping -c 5 10.0.2.1")
+    router.wait_until_succeeds("ping -c 5 10.0.2.100")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
new file mode 100644
index 000000000000..1e55341657bd
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
@@ -0,0 +1,331 @@
+# This test verifies that we can request and assign IPv6 prefixes from upstream
+# (e.g. ISP) routers.
+# The setup consists of three VMs. One for the ISP, as your residential router
+# and the third as a client machine in the residential network.
+#
+# There are two VLANs in this test:
+# - VLAN 1 is the connection between the ISP and the router
+# - VLAN 2 is the connection between the router and the client
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "systemd-networkd-ipv6-prefix-delegation";
+  meta = with lib.maintainers; {
+    maintainers = [ andir hexa ];
+  };
+  nodes = {
+
+    # The ISP's routers job is to delegate IPv6 prefixes via DHCPv6. Like with
+    # regular IPv6 auto-configuration it will also emit IPv6 router
+    # advertisements (RAs). Those RA's will not carry a prefix but in contrast
+    # just set the "Other" flag to indicate to the receiving nodes that they
+    # should attempt DHCPv6.
+    #
+    # Note: On the ISPs device we don't really care if we are using networkd in
+    # this example. That being said we can't use it (yet) as networkd doesn't
+    # implement the serving side of DHCPv6. We will use ISC Kea for that task.
+    isp = { lib, pkgs, ... }: {
+      virtualisation.vlans = [ 1 ];
+      networking = {
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1 = lib.mkForce {}; # Don't use scripted networking
+      };
+
+      systemd.network = {
+        enable = true;
+
+        networks = {
+          "eth1" = {
+            matchConfig.Name = "eth1";
+            address = [
+              "2001:DB8::1/64"
+            ];
+            networkConfig.IPForward = true;
+          };
+        };
+      };
+
+      # Since we want to program the routes that we delegate to the "customer"
+      # into our routing table we must provide kea with the required capability.
+      systemd.services.kea-dhcp6-server.serviceConfig = {
+        AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+        CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
+      };
+
+      services = {
+        # Configure the DHCPv6 server to hand out both IA_NA and IA_PD.
+        #
+        # We will hand out /48 prefixes from the subnet 2001:DB8:F000::/36.
+        # That gives us ~8k prefixes. That should be enough for this test.
+        #
+        # Since (usually) you will not receive a prefix with the router
+        # advertisements we also hand out /128 leases from the range
+        # 2001:DB8:0000:0000:FFFF::/112.
+        kea.dhcp6 = {
+          enable = true;
+          settings = {
+            interfaces-config.interfaces = [ "eth1" ];
+            subnet6 = [ {
+              interface = "eth1";
+              subnet = "2001:DB8::/32";
+              pd-pools = [ {
+                prefix = "2001:DB8:1000::";
+                prefix-len = 36;
+                delegated-len = 48;
+              } ];
+              pools = [ {
+                pool = "2001:DB8:0000:0000::-2001:DB8:0FFF:FFFF::FFFF";
+              } ];
+            } ];
+
+            # This is the glue between Kea and the Kernel FIB. DHCPv6
+            # rightfully has no concept of setting up a route in your
+            # FIB. This step really depends on your setup.
+            #
+            # In a production environment your DHCPv6 server is likely
+            # not the router. You might want to consider BGP, NETCONF
+            # calls, … in those cases.
+            #
+            # In this example we use the run script hook, that lets use
+            # execute anything and passes information via the environment.
+            # https://kea.readthedocs.io/en/kea-2.2.0/arm/hooks.html#run-script-run-script-support-for-external-hook-scripts
+            hooks-libraries = [ {
+              library = "${pkgs.kea}/lib/kea/hooks/libdhcp_run_script.so";
+              parameters = {
+                name = pkgs.writeShellScript "kea-run-hooks" ''
+                  export PATH="${lib.makeBinPath (with pkgs; [ coreutils iproute2 ])}"
+
+                  set -euxo pipefail
+
+                  leases6_committed() {
+                    for i in $(seq $LEASES6_SIZE); do
+                      idx=$((i-1))
+                      prefix_var="LEASES6_AT''${idx}_ADDRESS"
+                      plen_var="LEASES6_AT''${idx}_PREFIX_LEN"
+
+                      ip -6 route replace ''${!prefix_var}/''${!plen_var} via $QUERY6_REMOTE_ADDR dev $QUERY6_IFACE_NAME
+                    done
+                  }
+
+                  unknown_handler() {
+                    echo "Unhandled function call ''${*}"
+                    exit 123
+                  }
+
+                  case "$1" in
+                      "leases6_committed")
+                          leases6_committed
+                          ;;
+                      *)
+                          unknown_handler "''${@}"
+                          ;;
+                  esac
+                '';
+                sync = false;
+              };
+            } ];
+          };
+        };
+
+        # Finally we have to set up the router advertisements. While we could be
+        # using networkd or bird for this task `radvd` is probably the most
+        # venerable of them all. It was made explicitly for this purpose and
+        # the configuration is much more straightforward than what networkd
+        # requires.
+        # As outlined above we will have to set the `Managed` flag as otherwise
+        # the clients will not know if they should do DHCPv6. (Some do
+        # anyway/always)
+        radvd = {
+          enable = true;
+          config = ''
+            interface eth1 {
+              AdvSendAdvert on;
+              AdvManagedFlag on;
+              AdvOtherConfigFlag off; # we don't really have DNS or NTP or anything like that to distribute
+              prefix ::/64 {
+                AdvOnLink on;
+                AdvAutonomous on;
+              };
+            };
+          '';
+        };
+
+      };
+    };
+
+    # This will be our (residential) router that receives the IPv6 prefix (IA_PD)
+    # and /128 (IA_NA) allocation.
+    #
+    # Here we will actually start using networkd.
+    router = {
+      virtualisation.vlans = [ 1 2 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+
+      boot.kernel.sysctl = {
+        # we want to forward packets from the ISP to the client and back.
+        "net.ipv6.conf.all.forwarding" = 1;
+      };
+
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        # Consider enabling this in production and generating firewall rules
+        # for fowarding/input from the configured interfaces so you do not have
+        # to manage multiple places
+        firewall.enable = false;
+      };
+
+      systemd.network = {
+        networks = {
+          # systemd-networkd will load the first network unit file
+          # that matches, ordered lexiographically by filename.
+          # /etc/systemd/network/{40-eth1,99-main}.network already
+          # exists. This network unit must be loaded for the test,
+          # however, hence why this network is named such.
+
+          # Configuration of the interface to the ISP.
+          # We must request accept RAs and request the PD prefix.
+          "01-eth1" = {
+            name = "eth1";
+            networkConfig = {
+              Description = "ISP interface";
+              IPv6AcceptRA = true;
+              #DHCP = false; # no need for legacy IP
+            };
+            linkConfig = {
+              # We care about this interface when talking about being "online".
+              # If this interface is in the `routable` state we can reach
+              # others and they should be able to reach us.
+              RequiredForOnline = "routable";
+            };
+            # This configures the DHCPv6 client part towards the ISPs DHCPv6 server.
+            dhcpV6Config = {
+              # We have to include a request for a prefix in our DHCPv6 client
+              # request packets.
+              # Otherwise the upstream DHCPv6 server wouldn't know if we want a
+              # prefix or not.  Note: On some installation it makes sense to
+              # always force that option on the DHPCv6 server since there are
+              # certain CPEs that are just not setting this field but happily
+              # accept the delegated prefix.
+              PrefixDelegationHint  = "::/48";
+            };
+            ipv6SendRAConfig = {
+              # Let networkd know that we would very much like to use DHCPv6
+              # to obtain the "managed" information. Not sure why they can't
+              # just take that from the upstream RAs.
+              Managed = true;
+            };
+          };
+
+          # Interface to the client. Here we should redistribute a /64 from
+          # the prefix we received from the ISP.
+          "01-eth2" = {
+            name = "eth2";
+            networkConfig = {
+              Description = "Client interface";
+              # The client shouldn't be allowed to send us RAs, that would be weird.
+              IPv6AcceptRA = false;
+
+              # Delegate prefixes from the DHCPv6 PD pool.
+              DHCPPrefixDelegation = true;
+              IPv6SendRA = true;
+            };
+
+            # In a production environment you should consider setting these as well:
+            # ipv6SendRAConfig = {
+              #EmitDNS = true;
+              #EmitDomains = true;
+              #DNS= = "fe80::1"; # or whatever "well known" IP your router will have on the inside.
+            # };
+
+            # This adds a "random" ULA prefix to the interface that is being
+            # advertised to the clients.
+            # Not used in this test.
+            # ipv6Prefixes = [
+            #   {
+            #     ipv6PrefixConfig = {
+            #       AddressAutoconfiguration = true;
+            #       PreferredLifetimeSec = 1800;
+            #       ValidLifetimeSec = 1800;
+            #     };
+            #   }
+            # ];
+          };
+
+          # finally we are going to add a static IPv6 unique local address to
+          # the "lo" interface.  This will serve as ICMPv6 echo target to
+          # verify connectivity from the client to the router.
+          "01-lo" = {
+            name = "lo";
+            addresses = [
+              { addressConfig.Address = "FD42::1/128"; }
+            ];
+          };
+        };
+      };
+    };
+
+    # This is the client behind the router. We should be receiving router
+    # advertisements for both the ULA and the delegated prefix.
+    # All we have to do is boot with the default (networkd) configuration.
+    client = {
+      virtualisation.vlans = [ 2 ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+      };
+    };
+  };
+
+  testScript = ''
+    # First start the router and wait for it it reach a state where we are
+    # certain networkd is up and it is able to send out RAs
+    router.start()
+    router.wait_for_unit("systemd-networkd.service")
+
+    # After that we can boot the client and wait for the network online target.
+    # Since we only care about IPv6 that should not involve waiting for legacy
+    # IP leases.
+    client.start()
+    client.systemctl("start network-online.target")
+    client.wait_for_unit("network-online.target")
+
+    # the static address on the router should not be reachable
+    client.wait_until_succeeds("ping -6 -c 1 FD42::1")
+
+    # the global IP of the ISP router should still not be a reachable
+    router.fail("ping -6 -c 1 2001:DB8::1")
+
+    # Once we have internal connectivity boot up the ISP
+    isp.start()
+
+    # Since for the ISP "being online" should have no real meaning we just
+    # wait for the target where all the units have been started.
+    # It probably still takes a few more seconds for all the RA timers to be
+    # fired etc..
+    isp.wait_for_unit("multi-user.target")
+
+    # wait until the uplink interface has a good status
+    router.systemctl("start network-online.target")
+    router.wait_for_unit("network-online.target")
+    router.wait_until_succeeds("ping -6 -c1 2001:DB8::1")
+
+    # shortly after that the client should have received it's global IPv6
+    # address and thus be able to ping the ISP
+    client.wait_until_succeeds("ping -6 -c1 2001:DB8::1")
+
+    # verify that we got a globally scoped address in eth1 from the
+    # documentation prefix
+    ip_output = client.succeed("ip --json -6 address show dev eth1")
+
+    import json
+
+    ip_json = json.loads(ip_output)[0]
+    assert any(
+        addr["local"].upper().startswith("2001:DB8:")
+        for addr in ip_json["addr_info"]
+        if addr["scope"] == "global"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-networkd-vrf.nix b/nixpkgs/nixos/tests/systemd-networkd-vrf.nix
new file mode 100644
index 000000000000..d4227526a30d
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-networkd-vrf.nix
@@ -0,0 +1,182 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+  inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+
+  mkNode = vlan: id: {
+    virtualisation.vlans = [ vlan ];
+    networking = {
+      useDHCP = false;
+      useNetworkd = true;
+    };
+
+    systemd.network = {
+      enable = true;
+
+      networks."10-eth${toString vlan}" = {
+        matchConfig.Name = "eth${toString vlan}";
+        linkConfig.RequiredForOnline = "no";
+        networkConfig = {
+          Address = "192.168.${toString vlan}.${toString id}/24";
+          IPForward = "yes";
+        };
+      };
+    };
+  };
+in {
+  name = "systemd-networkd-vrf";
+  meta.maintainers = with lib.maintainers; [ ma27 ];
+
+  nodes = {
+    client = { pkgs, ... }: {
+      virtualisation.vlans = [ 1 2 ];
+
+      networking = {
+        useDHCP = false;
+        useNetworkd = true;
+        firewall.checkReversePath = "loose";
+      };
+
+      systemd.network = {
+        enable = true;
+
+        netdevs."10-vrf1" = {
+          netdevConfig = {
+            Kind = "vrf";
+            Name = "vrf1";
+            MTUBytes = "1300";
+          };
+          vrfConfig.Table = 23;
+        };
+        netdevs."10-vrf2" = {
+          netdevConfig = {
+            Kind = "vrf";
+            Name = "vrf2";
+            MTUBytes = "1300";
+          };
+          vrfConfig.Table = 42;
+        };
+
+        networks."10-vrf1" = {
+          matchConfig.Name = "vrf1";
+          networkConfig.IPForward = "yes";
+          routes = [
+            { routeConfig = { Destination = "192.168.1.2"; Metric = 100; }; }
+          ];
+        };
+        networks."10-vrf2" = {
+          matchConfig.Name = "vrf2";
+          networkConfig.IPForward = "yes";
+          routes = [
+            { routeConfig = { Destination = "192.168.2.3"; Metric = 100; }; }
+          ];
+        };
+
+        networks."10-eth1" = {
+          matchConfig.Name = "eth1";
+          linkConfig.RequiredForOnline = "no";
+          networkConfig = {
+            VRF = "vrf1";
+            Address = "192.168.1.1/24";
+            IPForward = "yes";
+          };
+        };
+        networks."10-eth2" = {
+          matchConfig.Name = "eth2";
+          linkConfig.RequiredForOnline = "no";
+          networkConfig = {
+            VRF = "vrf2";
+            Address = "192.168.2.1/24";
+            IPForward = "yes";
+          };
+        };
+      };
+    };
+
+    node1 = lib.mkMerge [
+      (mkNode 1 2)
+      {
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+      }
+    ];
+
+    node2 = mkNode 2 3;
+    node3 = mkNode 2 4;
+  };
+
+  testScript = ''
+    import json
+
+    def compare(raw_json, to_compare):
+        data = json.loads(raw_json)
+        assert len(raw_json) >= len(to_compare)
+        for i, row in enumerate(to_compare):
+            actual = data[i]
+            assert len(row.keys()) > 0
+            for key, value in row.items():
+                assert value == actual[key], f"""
+                  In entry {i}, value {key}: got: {actual[key]}, expected {value}
+                """
+
+
+    start_all()
+
+    client.wait_for_unit("network.target")
+    node1.wait_for_unit("network.target")
+    node2.wait_for_unit("network.target")
+    node3.wait_for_unit("network.target")
+
+    # Check that networkd properly configures the main routing table
+    # and the routing tables for the VRF.
+    with subtest("check vrf routing tables"):
+        compare(
+            client.succeed("ip --json -4 route list"),
+            [
+                {"dst": "192.168.1.2", "dev": "vrf1", "metric": 100},
+                {"dst": "192.168.2.3", "dev": "vrf2", "metric": 100}
+            ]
+        )
+        compare(
+            client.succeed("ip --json -4 route list table 23"),
+            [
+                {"dst": "192.168.1.0/24", "dev": "eth1", "prefsrc": "192.168.1.1"},
+                {"type": "local", "dst": "192.168.1.1", "dev": "eth1", "prefsrc": "192.168.1.1"},
+                {"type": "broadcast", "dev": "eth1", "prefsrc": "192.168.1.1", "dst": "192.168.1.255"}
+            ]
+        )
+        compare(
+            client.succeed("ip --json -4 route list table 42"),
+            [
+                {"dst": "192.168.2.0/24", "dev": "eth2", "prefsrc": "192.168.2.1"},
+                {"type": "local", "dst": "192.168.2.1", "dev": "eth2", "prefsrc": "192.168.2.1"},
+                {"type": "broadcast", "dev": "eth2", "prefsrc": "192.168.2.1", "dst": "192.168.2.255"}
+            ]
+        )
+
+    # Ensure that other nodes are reachable via ICMP through the VRF.
+    with subtest("icmp through vrf works"):
+        client.succeed("ping -c5 192.168.1.2")
+        client.succeed("ping -c5 192.168.2.3")
+
+    # Test whether TCP through a VRF IP is possible.
+    with subtest("tcp traffic through vrf works"):
+        node1.wait_for_open_port(22)
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ulimit -l 2048; ip vrf exec vrf1 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true"
+        )
+
+    # Only configured routes through the VRF from the main routing table should
+    # work. Additional IPs are only reachable when binding to the vrf interface.
+    with subtest("only routes from main routing table work by default"):
+        client.fail("ping -c5 192.168.2.4")
+        client.succeed("ping -I vrf2 -c5 192.168.2.4")
+
+    client.shutdown()
+    node1.shutdown()
+    node2.shutdown()
+    node3.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-networkd.nix b/nixpkgs/nixos/tests/systemd-networkd.nix
new file mode 100644
index 000000000000..6b241b93d511
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-networkd.nix
@@ -0,0 +1,123 @@
+let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: {
+      imports = [ common/user-account.nix ];
+      systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
+      networking.useNetworkd = true;
+      networking.useDHCP = false;
+      networking.firewall.enable = false;
+      virtualisation.vlans = [ 1 ];
+      environment.systemPackages = with pkgs; [ wireguard-tools ];
+      systemd.network = {
+        enable = true;
+        config = {
+          routeTables.custom = 23;
+        };
+        netdevs = {
+          "90-wg0" = {
+            netdevConfig = { Kind = "wireguard"; Name = "wg0"; };
+            wireguardConfig = {
+              # NOTE: we're storing the wireguard private key in the
+              #       store for this test. Do not do this in the real
+              #       world. Keep in mind the nix store is
+              #       world-readable.
+              PrivateKeyFile = pkgs.writeText "wg0-priv" privk;
+              ListenPort = 51820;
+              FirewallMark = 42;
+            };
+            wireguardPeers = [ {wireguardPeerConfig={
+              Endpoint = "192.168.1.${peerId}:51820";
+              PublicKey = pubk;
+              PresharedKeyFile = pkgs.writeText "psk.key" "yTL3sCOL33Wzi6yCnf9uZQl/Z8laSE+zwpqOHC4HhFU=";
+              AllowedIPs = [ "10.0.0.${peerId}/32" ];
+              PersistentKeepalive = 15;
+            };}];
+          };
+        };
+        networks = {
+          "99-nope" = {
+            matchConfig.Name = "eth*";
+            linkConfig.Unmanaged = true;
+          };
+          "90-wg0" = {
+            matchConfig = { Name = "wg0"; };
+            address = [ "10.0.0.${nodeId}/32" ];
+            routes = [
+              { routeConfig = { Gateway = "10.0.0.${nodeId}"; Destination = "10.0.0.0/24"; }; }
+              { routeConfig = { Gateway = "10.0.0.${nodeId}"; Destination = "10.0.0.0/24"; Table = "custom"; }; }
+            ];
+          };
+          "30-eth1" = {
+            matchConfig = { Name = "eth1"; };
+            address = [
+              "192.168.1.${nodeId}/24"
+              "fe80::${nodeId}/64"
+            ];
+            routingPolicyRules = [
+              { routingPolicyRuleConfig = { Table = 10; IncomingInterface = "eth1"; Family = "both"; };}
+              { routingPolicyRuleConfig = { Table = 20; OutgoingInterface = "eth1"; };}
+              { routingPolicyRuleConfig = { Table = 30; From = "192.168.1.1"; To = "192.168.1.2"; SourcePort = 666 ; DestinationPort = 667; };}
+              { routingPolicyRuleConfig = { Table = 40; IPProtocol = "tcp"; InvertRule = true; };}
+              { routingPolicyRuleConfig = { Table = 50; IncomingInterface = "eth1"; Family = "ipv4"; };}
+            ];
+          };
+        };
+      };
+    };
+in import ./make-test-python.nix ({pkgs, ... }: {
+  name = "networkd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ picnoir ];
+  };
+  nodes = {
+    node1 = { pkgs, ... }@attrs:
+    let localConf = {
+        privk = "GDiXWlMQKb379XthwX0haAbK6hTdjblllpjGX0heP00=";
+        pubk = "iRxpqj42nnY0Qz8MAQbSm7bXxXP5hkPqWYIULmvW+EE=";
+        nodeId = "1";
+        peerId = "2";
+    };
+    in generateNodeConf (attrs // localConf);
+
+    node2 = { pkgs, ... }@attrs:
+    let localConf = {
+        privk = "eHxSI2jwX/P4AOI0r8YppPw0+4NZnjOxfbS5mt06K2k=";
+        pubk = "27s0OvaBBdHoJYkH9osZpjpgSOVNw+RaKfboT/Sfq0g=";
+        nodeId = "2";
+        peerId = "1";
+    };
+    in generateNodeConf (attrs // localConf);
+  };
+testScript = ''
+    start_all()
+    node1.wait_for_unit("systemd-networkd-wait-online.service")
+    node2.wait_for_unit("systemd-networkd-wait-online.service")
+
+    # ================================
+    # Networkd Config
+    # ================================
+    node1.succeed("grep RouteTable=custom:23 /etc/systemd/networkd.conf")
+    node1.succeed("sudo ip route show table custom | grep '10.0.0.0/24 via 10.0.0.1 dev wg0 proto static'")
+
+    # ================================
+    # Wireguard
+    # ================================
+    node1.succeed("ping -c 5 10.0.0.2")
+    node2.succeed("ping -c 5 10.0.0.1")
+    # Is the fwmark set?
+    node2.succeed("wg | grep -q 42")
+
+    # ================================
+    # Routing Policies
+    # ================================
+    # Testing all the routingPolicyRuleConfig members:
+    # Table + IncomingInterface
+    node1.succeed("sudo ip rule | grep 'from all iif eth1 lookup 10'")
+    # OutgoingInterface
+    node1.succeed("sudo ip rule | grep 'from all oif eth1 lookup 20'")
+    # From + To + SourcePort + DestinationPort
+    node1.succeed(
+        "sudo ip rule | grep 'from 192.168.1.1 to 192.168.1.2 sport 666 dport 667 lookup 30'"
+    )
+    # IPProtocol + InvertRule
+    node1.succeed("sudo ip rule | grep 'not from all ipproto tcp lookup 40'")
+'';
+})
diff --git a/nixpkgs/nixos/tests/systemd-no-tainted.nix b/nixpkgs/nixos/tests/systemd-no-tainted.nix
new file mode 100644
index 000000000000..f0504065f2a4
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-no-tainted.nix
@@ -0,0 +1,14 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "systemd-no-tainted";
+
+  nodes.machine = { };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    with subtest("systemctl should not report tainted with unmerged-usr"):
+        output = machine.succeed("systemctl status")
+        print(output)
+        assert "Tainted" not in output
+        assert "unmerged-usr" not in output
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-nspawn-configfile.nix b/nixpkgs/nixos/tests/systemd-nspawn-configfile.nix
new file mode 100644
index 000000000000..12ab21b7f9b5
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-nspawn-configfile.nix
@@ -0,0 +1,128 @@
+import ./make-test-python.nix ({ lib, ... }:
+let
+  execOptions = [
+    "Boot"
+    "ProcessTwo"
+    "Parameters"
+    "Environment"
+    "User"
+    "WorkingDirectory"
+    "PivotRoot"
+    "Capability"
+    "DropCapability"
+    "NoNewPrivileges"
+    "KillSignal"
+    "Personality"
+    "MachineID"
+    "PrivateUsers"
+    "NotifyReady"
+    "SystemCallFilter"
+    "LimitCPU"
+    "LimitFSIZE"
+    "LimitDATA"
+    "LimitSTACK"
+    "LimitCORE"
+    "LimitRSS"
+    "LimitNOFILE"
+    "LimitAS"
+    "LimitNPROC"
+    "LimitMEMLOCK"
+    "LimitLOCKS"
+    "LimitSIGPENDING"
+    "LimitMSGQUEUE"
+    "LimitNICE"
+    "LimitRTPRIO"
+    "LimitRTTIME"
+    "OOMScoreAdjust"
+    "CPUAffinity"
+    "Hostname"
+    "ResolvConf"
+    "Timezone"
+    "LinkJournal"
+    "Ephemeral"
+    "AmbientCapability"
+  ];
+
+  filesOptions = [
+    "ReadOnly"
+    "Volatile"
+    "Bind"
+    "BindReadOnly"
+    "TemporaryFileSystem"
+    "Overlay"
+    "OverlayReadOnly"
+    "PrivateUsersChown"
+    "BindUser"
+    "Inaccessible"
+    "PrivateUsersOwnership"
+  ];
+
+  networkOptions = [
+    "Private"
+    "VirtualEthernet"
+    "VirtualEthernetExtra"
+    "Interface"
+    "MACVLAN"
+    "IPVLAN"
+    "Bridge"
+    "Zone"
+    "Port"
+  ];
+
+  optionsToConfig = opts: builtins.listToAttrs (map (n: lib.nameValuePair n "testdata") opts);
+
+  grepForOptions = opts: ''node.succeed(
+    "for o in ${builtins.concatStringsSep " " opts} ; do grep --quiet $o ${configFile} || exit 1 ; done"
+  )'';
+
+  unitName = "options-test";
+  configFile = "/etc/systemd/nspawn/${unitName}.nspawn";
+
+in
+{
+  name = "systemd-nspawn-configfile";
+
+  nodes = {
+    node = { pkgs, ... }: {
+      systemd.nspawn."${unitName}" = {
+        enable = true;
+
+        execConfig = optionsToConfig execOptions // {
+          Boot = true;
+          ProcessTwo = true;
+          NotifyReady = true;
+        };
+
+        filesConfig = optionsToConfig filesOptions // {
+          ReadOnly = true;
+          Volatile = "state";
+          PrivateUsersChown = true;
+          PrivateUsersOwnership = "auto";
+        };
+
+        networkConfig = optionsToConfig networkOptions // {
+          Private = true;
+          VirtualEthernet = true;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    node.wait_for_file("${configFile}")
+
+    with subtest("Test for presence of all specified options in config file"):
+      ${grepForOptions execOptions}
+      ${grepForOptions filesOptions}
+      ${grepForOptions networkOptions}
+
+    with subtest("Test for absence of misspelled option 'MachineId' (instead of 'MachineID')"):
+      node.fail("grep --quiet MachineId ${configFile}")
+  '';
+
+  meta.maintainers = [
+    lib.maintainers.zi3m5f
+  ];
+})
diff --git a/nixpkgs/nixos/tests/systemd-nspawn.nix b/nixpkgs/nixos/tests/systemd-nspawn.nix
new file mode 100644
index 000000000000..b86762233d18
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-nspawn.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({pkgs, lib, ...}:
+let
+  gpgKeyring = import ./common/gpg-keyring.nix { inherit pkgs; };
+
+  nspawnImages = (pkgs.runCommand "localhost" { buildInputs = [ pkgs.coreutils pkgs.gnupg ]; } ''
+    mkdir -p $out
+    cd $out
+
+    # produce a testimage.raw
+    dd if=/dev/urandom of=$out/testimage.raw bs=$((1024*1024+7)) count=5
+
+    # produce a testimage2.tar.xz, containing the hello store path
+    tar cvJpf testimage2.tar.xz ${pkgs.hello}
+
+    # produce signature(s)
+    sha256sum testimage* > SHA256SUMS
+    export GNUPGHOME="$(mktemp -d)"
+    cp -R ${gpgKeyring}/* $GNUPGHOME
+    gpg --batch --sign --detach-sign --output SHA256SUMS.gpg SHA256SUMS
+  '');
+in {
+  name = "systemd-nspawn";
+
+  nodes = {
+    server = { pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      services.nginx = {
+        enable = true;
+        virtualHosts."server".root = nspawnImages;
+      };
+    };
+    client = { pkgs, ... }: {
+      environment.etc."systemd/import-pubring.gpg".source = "${gpgKeyring}/pubkey.gpg";
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("nginx.service")
+    client.systemctl("start network-online.target")
+    client.wait_for_unit("network-online.target")
+    client.succeed("machinectl pull-raw --verify=signature http://server/testimage.raw")
+    client.succeed(
+        "cmp /var/lib/machines/testimage.raw ${nspawnImages}/testimage.raw"
+    )
+    client.succeed("machinectl pull-tar --verify=signature http://server/testimage2.tar.xz")
+    client.succeed(
+        "cmp /var/lib/machines/testimage2/${pkgs.hello}/bin/hello ${pkgs.hello}/bin/hello"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-oomd.nix b/nixpkgs/nixos/tests/systemd-oomd.nix
new file mode 100644
index 000000000000..55c4c1350000
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-oomd.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+  name = "systemd-oomd";
+
+  # This test is a simplified version of systemd's testsuite-55.
+  # https://github.com/systemd/systemd/blob/v251/test/units/testsuite-55.sh
+  nodes.machine = { pkgs, ... }: {
+    # Limit VM resource usage.
+    virtualisation.memorySize = 1024;
+    systemd.oomd.extraConfig.DefaultMemoryPressureDurationSec = "1s";
+
+    systemd.slices.workload = {
+      description = "Test slice for memory pressure kills";
+      sliceConfig = {
+        MemoryAccounting = true;
+        ManagedOOMMemoryPressure = "kill";
+        ManagedOOMMemoryPressureLimit = "10%";
+      };
+    };
+
+    systemd.services.testbloat = {
+      description = "Create a lot of memory pressure";
+      serviceConfig = {
+        Slice = "workload.slice";
+        MemoryHigh = "5M";
+        ExecStart = "${pkgs.coreutils}/bin/tail /dev/zero";
+      };
+    };
+
+    systemd.services.testchill = {
+      description = "No memory pressure";
+      serviceConfig = {
+        Slice = "workload.slice";
+        MemoryHigh = "3M";
+        ExecStart = "${pkgs.coreutils}/bin/sleep infinity";
+      };
+    };
+  };
+
+  testScript = ''
+    # Start the system.
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("oomctl")
+
+    machine.succeed("systemctl start testchill.service")
+    with subtest("OOMd should kill the bad service"):
+        machine.fail("systemctl start --wait testbloat.service")
+        assert machine.get_unit_info("testbloat.service")["Result"] == "oom-kill"
+
+    with subtest("Service without memory pressure should be untouched"):
+        machine.require_unit_state("testchill.service", "active")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-portabled.nix b/nixpkgs/nixos/tests/systemd-portabled.nix
new file mode 100644
index 000000000000..ef38258b0d86
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-portabled.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({pkgs, lib, ...}: let
+  demo-program = pkgs.writeShellScriptBin "demo" ''
+      while ${pkgs.coreutils}/bin/sleep 3; do
+          echo Hello World > /dev/null
+      done
+  '';
+  demo-service = pkgs.writeText "demo.service" ''
+    [Unit]
+    Description=demo service
+    Requires=demo.socket
+    After=demo.socket
+
+    [Service]
+    Type=simple
+    ExecStart=${demo-program}/bin/demo
+    Restart=always
+
+    [Install]
+    WantedBy=multi-user.target
+    Also=demo.socket
+  '';
+  demo-socket = pkgs.writeText "demo.socket" ''
+    [Unit]
+    Description=demo socket
+
+    [Socket]
+    ListenStream=/run/demo.sock
+    SocketMode=0666
+
+    [Install]
+    WantedBy=sockets.target
+  '';
+  demo-portable = pkgs.portableService {
+    pname = "demo";
+    version = "1.0";
+    description = ''A demo "Portable Service" for a shell program built with nix'';
+    units = [ demo-service demo-socket ];
+  };
+in {
+
+  name = "systemd-portabled";
+  nodes.machine = {};
+  testScript = ''
+    machine.succeed("portablectl")
+    machine.wait_for_unit("systemd-portabled.service")
+    machine.succeed("portablectl attach --now --runtime ${demo-portable}/demo_1.0.raw")
+    machine.wait_for_unit("demo.service")
+    machine.succeed("portablectl detach --now --runtime demo_1.0")
+    machine.fail("systemctl status demo.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-repart.nix b/nixpkgs/nixos/tests/systemd-repart.nix
new file mode 100644
index 000000000000..3914d5b32397
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-repart.nix
@@ -0,0 +1,182 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  # A testScript fragment that prepares a disk with some empty, unpartitioned
+  # space. and uses it to boot the test with. Takes a single argument `machine`
+  # from which the diskImage is extracted.
+  useDiskImage = machine: ''
+    import os
+    import shutil
+    import subprocess
+    import tempfile
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    shutil.copyfile("${machine.system.build.diskImage}/nixos.img", tmp_disk_image.name)
+
+    subprocess.run([
+      "${machine.config.virtualisation.qemu.package}/bin/qemu-img",
+      "resize",
+      "-f",
+      "raw",
+      tmp_disk_image.name,
+      "+32M",
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+  '';
+
+  common = { config, pkgs, lib, ... }: {
+    virtualisation.useDefaultFilesystems = false;
+    virtualisation.fileSystems = {
+      "/" = {
+        device = "/dev/vda2";
+        fsType = "ext4";
+      };
+    };
+
+    # systemd-repart operates on disks with a partition table. The qemu module,
+    # however, creates separate filesystem images without a partition table, so
+    # we have to create a disk image manually.
+    #
+    # This creates two partitions, an ESP available as /dev/vda1 and the root
+    # partition available as /dev/vda2.
+    system.build.diskImage = import ../lib/make-disk-image.nix {
+      inherit config pkgs lib;
+      # Use a raw format disk so that it can be resized before starting the
+      # test VM.
+      format = "raw";
+      # Keep the image as small as possible but leave some room for changes.
+      bootSize = "32M";
+      additionalSpace = "0M";
+      # GPT with an EFI System Partition is the typical use case for
+      # systemd-repart because it does not support MBR.
+      partitionTableType = "efi";
+      # We do not actually care much about the content of the partitions, so we
+      # do not need a bootloader installed.
+      installBootLoader = false;
+      # Improve determinism by not copying a channel.
+      copyChannel = false;
+    };
+  };
+in
+{
+  basic = makeTest {
+    name = "systemd-repart";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      imports = [ common ];
+
+      boot.initrd.systemd.enable = true;
+
+      boot.initrd.systemd.repart.enable = true;
+      systemd.repart.partitions = {
+        "10-root" = {
+          Type = "linux-generic";
+        };
+      };
+    };
+
+    testScript = { nodes, ... }: ''
+      ${useDiskImage nodes.machine}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
+      assert "Growing existing partition 1." in systemd_repart_logs
+    '';
+  };
+
+  after-initrd = makeTest {
+    name = "systemd-repart-after-initrd";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      imports = [ common ];
+
+      systemd.repart.enable = true;
+      systemd.repart.partitions = {
+        "10-root" = {
+          Type = "linux-generic";
+        };
+      };
+    };
+
+    testScript = { nodes, ... }: ''
+      ${useDiskImage nodes.machine}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      systemd_repart_logs = machine.succeed("journalctl --unit systemd-repart.service")
+      assert "Growing existing partition 1." in systemd_repart_logs
+    '';
+  };
+
+  create-root = makeTest {
+    name = "systemd-repart-create-root";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, lib, pkgs, ... }: {
+      virtualisation.useDefaultFilesystems = false;
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-partlabel/created-root";
+          fsType = "ext4";
+        };
+        "/nix/store" = {
+          device = "/dev/vda2";
+          fsType = "ext4";
+        };
+      };
+
+      # Create an image containing only the Nix store. This enables creating
+      # the root partition with systemd-repart and then successfully booting
+      # into a working system.
+      #
+      # This creates two partitions, an ESP available as /dev/vda1 and the Nix
+      # store available as /dev/vda2.
+      system.build.diskImage = import ../lib/make-disk-image.nix {
+        inherit config pkgs lib;
+        onlyNixStore = true;
+        format = "raw";
+        bootSize = "32M";
+        additionalSpace = "0M";
+        partitionTableType = "efi";
+        installBootLoader = false;
+        copyChannel = false;
+      };
+
+      boot.initrd.systemd.enable = true;
+
+      boot.initrd.systemd.repart.enable = true;
+      boot.initrd.systemd.repart.device = "/dev/vda";
+      systemd.repart.partitions = {
+        "10-root" = {
+          Type = "root";
+          Label = "created-root";
+          Format = "ext4";
+        };
+      };
+    };
+
+    testScript = { nodes, ... }: ''
+      ${useDiskImage nodes.machine}
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
+      assert "Adding new partition 2 to partition table." in systemd_repart_logs
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/systemd-shutdown.nix b/nixpkgs/nixos/tests/systemd-shutdown.nix
new file mode 100644
index 000000000000..ca6754046f57
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-shutdown.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, systemdStage1 ? false, ...} : let
+  msg = "Shutting down NixOS";
+in {
+  name = "systemd-shutdown";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ das_j ];
+  };
+
+  nodes.machine = {
+    imports = [ ../modules/profiles/minimal.nix ];
+    systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/shutdown-message".source = pkgs.writeShellScript "shutdown-message" ''
+      echo "${msg}"
+    '';
+    boot.initrd.systemd.enable = systemdStage1;
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    # .shutdown() would wait for the machine to power off
+    machine.succeed("systemctl poweroff")
+    # Message printed by systemd-shutdown
+    machine.wait_for_console_text("Unmounting '/oldroot'")
+    machine.wait_for_console_text("${msg}")
+    # Don't try to sync filesystems
+    machine.wait_for_shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-sysupdate.nix b/nixpkgs/nixos/tests/systemd-sysupdate.nix
new file mode 100644
index 000000000000..6592764c9ff4
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-sysupdate.nix
@@ -0,0 +1,66 @@
+# Tests downloading a signed update aritfact from a server to a target machine.
+# This test does not rely on the `systemd.timer` units provided by the
+# `systemd-sysupdate` module but triggers the `systemd-sysupdate` service
+# manually to make the test more robust.
+
+{ lib, pkgs, ... }:
+
+let
+  gpgKeyring = import ./common/gpg-keyring.nix { inherit pkgs; };
+in
+{
+  name = "systemd-sysupdate";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes = {
+    server = { pkgs, ... }: {
+      networking.firewall.enable = false;
+      services.nginx = {
+        enable = true;
+        virtualHosts."server" = {
+          root = pkgs.runCommand "sysupdate-artifacts" { buildInputs = [ pkgs.gnupg ]; } ''
+            mkdir -p $out
+            cd $out
+
+            echo "nixos" > nixos_1.txt
+            sha256sum nixos_1.txt > SHA256SUMS
+
+            export GNUPGHOME="$(mktemp -d)"
+            cp -R ${gpgKeyring}/* $GNUPGHOME
+
+            gpg --batch --sign --detach-sign --output SHA256SUMS.gpg SHA256SUMS
+          '';
+        };
+      };
+    };
+
+    target = {
+      systemd.sysupdate = {
+        enable = true;
+        transfers = {
+          "text-file" = {
+            Source = {
+              Type = "url-file";
+              Path = "http://server/";
+              MatchPattern = "nixos_@v.txt";
+            };
+            Target = {
+              Path = "/";
+              MatchPattern = [ "nixos_@v.txt" ];
+            };
+          };
+        };
+      };
+
+      environment.etc."systemd/import-pubring.gpg".source = "${gpgKeyring}/pubkey.gpg";
+    };
+  };
+
+  testScript = ''
+    server.wait_for_unit("nginx.service")
+
+    target.succeed("systemctl start systemd-sysupdate")
+    assert "nixos" in target.wait_until_succeeds("cat /nixos_1.txt", timeout=5)
+  '';
+}
diff --git a/nixpkgs/nixos/tests/systemd-sysusers-immutable.nix b/nixpkgs/nixos/tests/systemd-sysusers-immutable.nix
new file mode 100644
index 000000000000..42cbf84d175e
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-sysusers-immutable.nix
@@ -0,0 +1,64 @@
+{ lib, ... }:
+
+let
+  rootPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
+  normaloPassword = "$y$j9T$3aiOV/8CADAK22OK2QT3/0$67OKd50Z4qTaZ8c/eRWHLIM.o3ujtC1.n9ysmJfv639";
+  newNormaloPassword = "mellow";
+in
+
+{
+
+  name = "activation-sysusers-immutable";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = {
+    systemd.sysusers.enable = true;
+    users.mutableUsers = false;
+
+    # Override the empty root password set by the test instrumentation
+    users.users.root.hashedPasswordFile = lib.mkForce null;
+    users.users.root.initialHashedPassword = rootPassword;
+    users.users.normalo = {
+      isNormalUser = true;
+      initialHashedPassword = normaloPassword;
+    };
+
+    specialisation.new-generation.configuration = {
+      users.users.new-normalo = {
+        isNormalUser = true;
+        initialPassword = newNormaloPassword;
+      };
+    };
+  };
+
+  testScript = ''
+    with subtest("Users are not created with systemd-sysusers"):
+      machine.fail("systemctl status systemd-sysusers.service")
+      machine.fail("ls /etc/sysusers.d")
+
+    with subtest("Correct mode on the password files"):
+      assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
+      assert machine.succeed("stat -c '%a' /etc/group") == "644\n"
+      assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n"
+      assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n"
+
+    with subtest("root user has correct password"):
+      print(machine.succeed("getent passwd root"))
+      assert "${rootPassword}" in machine.succeed("getent shadow root"), "root user password is not correct"
+
+    with subtest("normalo user is created"):
+      print(machine.succeed("getent passwd normalo"))
+      assert machine.succeed("stat -c '%U' /home/normalo") == "normalo\n"
+      assert "${normaloPassword}" in machine.succeed("getent shadow normalo"), "normalo user password is not correct"
+
+
+    machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+
+    with subtest("new-normalo user is created after switching to new generation"):
+      print(machine.succeed("getent passwd new-normalo"))
+      print(machine.succeed("getent shadow new-normalo"))
+      assert machine.succeed("stat -c '%U' /home/new-normalo") == "new-normalo\n"
+  '';
+}
diff --git a/nixpkgs/nixos/tests/systemd-sysusers-mutable.nix b/nixpkgs/nixos/tests/systemd-sysusers-mutable.nix
new file mode 100644
index 000000000000..e69cfe23a59a
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-sysusers-mutable.nix
@@ -0,0 +1,71 @@
+{ lib, ... }:
+
+let
+  rootPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
+  normaloPassword = "hello";
+  newNormaloPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
+in
+
+{
+
+  name = "activation-sysusers-mutable";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { pkgs, ... }: {
+    systemd.sysusers.enable = true;
+    users.mutableUsers = true;
+
+    # Prerequisites
+    system.etc.overlay.enable = true;
+    boot.initrd.systemd.enable = true;
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    # Override the empty root password set by the test instrumentation
+    users.users.root.hashedPasswordFile = lib.mkForce null;
+    users.users.root.initialHashedPassword = rootPassword;
+    users.users.normalo = {
+      isNormalUser = true;
+      initialPassword = normaloPassword;
+    };
+
+    specialisation.new-generation.configuration = {
+      users.users.new-normalo = {
+        isNormalUser = true;
+        initialHashedPassword = newNormaloPassword;
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("systemd-sysusers.service")
+
+    with subtest("systemd-sysusers.service contains the credentials"):
+      sysusers_service = machine.succeed("systemctl cat systemd-sysusers.service")
+      print(sysusers_service)
+      assert "SetCredential=passwd.plaintext-password.normalo:${normaloPassword}" in sysusers_service
+
+    with subtest("Correct mode on the password files"):
+      assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
+      assert machine.succeed("stat -c '%a' /etc/group") == "644\n"
+      assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n"
+      assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n"
+
+    with subtest("root user has correct password"):
+      print(machine.succeed("getent passwd root"))
+      assert "${rootPassword}" in machine.succeed("getent shadow root"), "root user password is not correct"
+
+    with subtest("normalo user is created"):
+      print(machine.succeed("getent passwd normalo"))
+      assert machine.succeed("stat -c '%U' /home/normalo") == "normalo\n"
+
+
+    machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+
+    with subtest("new-normalo user is created after switching to new generation"):
+      print(machine.succeed("getent passwd new-normalo"))
+      assert machine.succeed("stat -c '%U' /home/new-normalo") == "new-normalo\n"
+      assert "${newNormaloPassword}" in machine.succeed("getent shadow new-normalo"), "new-normalo user password is not correct"
+  '';
+}
diff --git a/nixpkgs/nixos/tests/systemd-timesyncd-nscd-dnssec.nix b/nixpkgs/nixos/tests/systemd-timesyncd-nscd-dnssec.nix
new file mode 100644
index 000000000000..697dd824e345
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-timesyncd-nscd-dnssec.nix
@@ -0,0 +1,61 @@
+# This test verifies that systemd-timesyncd can resolve the NTP server hostname when DNSSEC validation
+# fails even though it is enforced in the systemd-resolved settings. It is required in order to solve
+# the chicken-and-egg problem when DNSSEC validation needs the correct time to work, but to set the
+# correct time, we need to connect to an NTP server, which usually requires resolving its hostname.
+#
+# This test does the following:
+# - Sets up a DNS server (tinydns) listening on the eth1 ip addess, serving .ntp and fake.ntp records.
+# - Configures that DNS server as a resolver and enables DNSSEC in systemd-resolved settings.
+# - Configures systemd-timesyncd to use fake.ntp hostname as an NTP server.
+# - Performs a regular DNS lookup, to ensure it fails due to broken DNSSEC.
+# - Waits until systemd-timesyncd resolves fake.ntp by checking its debug output.
+#   Here, we don't expect systemd-timesyncd to connect and synchronize time because there is no NTP
+#   server running. For this test to succeed, we only need to ensure that systemd-timesyncd
+#   resolves the IP address of the fake.ntp host.
+
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  ntpHostname = "fake.ntp";
+  ntpIP = "192.0.2.1";
+in
+{
+  name = "systemd-timesyncd";
+  nodes.machine = { pkgs, lib, config, ... }:
+    let
+      eth1IP = (lib.head config.networking.interfaces.eth1.ipv4.addresses).address;
+    in
+    {
+      # Setup a local DNS server for the NTP domain on the eth1 IP address
+      services.tinydns = {
+        enable = true;
+        ip = eth1IP;
+        data = ''
+          .ntp:${eth1IP}
+          +.${ntpHostname}:${ntpIP}
+        '';
+      };
+
+      # Enable systemd-resolved with DNSSEC and use the local DNS as a name server
+      services.resolved.enable = true;
+      services.resolved.dnssec = "true";
+      networking.nameservers = [ eth1IP ];
+
+      # Configure systemd-timesyncd to use our NTP hostname
+      services.timesyncd.enable = lib.mkForce true;
+      services.timesyncd.servers = [ ntpHostname ];
+      services.timesyncd.extraConfig = ''
+        FallbackNTP=${ntpHostname}
+      '';
+
+      # The debug output is necessary to determine whether systemd-timesyncd successfully resolves our NTP hostname or not
+      systemd.services.systemd-timesyncd.environment.SYSTEMD_LOG_LEVEL = "debug";
+    };
+
+  testScript = ''
+    machine.wait_for_unit("tinydns.service")
+    machine.wait_for_unit("systemd-timesyncd.service")
+    machine.fail("resolvectl query ${ntpHostname}")
+    machine.wait_until_succeeds("journalctl -u systemd-timesyncd.service --grep='Resolved address ${ntpIP}:123 for ${ntpHostname}'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-timesyncd.nix b/nixpkgs/nixos/tests/systemd-timesyncd.nix
new file mode 100644
index 000000000000..02f49f49b8a5
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-timesyncd.nix
@@ -0,0 +1,54 @@
+# Regression test for systemd-timesync having moved the state directory without
+# upstream providing a migration path. https://github.com/systemd/systemd/issues/12131
+
+import ./make-test-python.nix (let
+  common = { lib, ... }: {
+    # override the `false` value from the qemu-vm base profile
+    services.timesyncd.enable = lib.mkForce true;
+  };
+  mkVM = conf: { imports = [ conf common ]; };
+in {
+  name = "systemd-timesyncd";
+  nodes = {
+    current = mkVM {};
+    pre1909 = mkVM ({lib, ... }: {
+      # create the path that should be migrated by our activation script when
+      # upgrading to a newer nixos version
+      system.stateVersion = "19.03";
+      systemd.tmpfiles.settings.systemd-timesyncd-test = {
+        "/var/lib/systemd/timesync".R = { };
+        "/var/lib/systemd/timesync".L.argument = "/var/lib/private/systemd/timesync";
+        "/var/lib/private/systemd/timesync".d = {
+          user = "systemd-timesync";
+          group = "systemd-timesync";
+        };
+      };
+    });
+  };
+
+  testScript = ''
+    start_all()
+    current.succeed("systemctl status systemd-timesyncd.service")
+    # on a new install with a recent systemd there should not be any
+    # leftovers from the dynamic user mess
+    current.succeed("test -e /var/lib/systemd/timesync")
+    current.succeed("test ! -L /var/lib/systemd/timesync")
+
+    # timesyncd should be running on the upgrading system since we fixed the
+    # file bits in the activation script
+    pre1909.succeed("systemctl status systemd-timesyncd.service")
+
+    # the path should be gone after the migration
+    pre1909.succeed("test ! -e /var/lib/private/systemd/timesync")
+
+    # and the new path should no longer be a symlink
+    pre1909.succeed("test -e /var/lib/systemd/timesync")
+    pre1909.succeed("test ! -L /var/lib/systemd/timesync")
+
+    # after a restart things should still work and not fail in the activation
+    # scripts and cause the boot to fail..
+    pre1909.shutdown()
+    pre1909.start()
+    pre1909.succeed("systemctl status systemd-timesyncd.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-user-tmpfiles-rules.nix b/nixpkgs/nixos/tests/systemd-user-tmpfiles-rules.nix
new file mode 100644
index 000000000000..bf29b4b57be3
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-user-tmpfiles-rules.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-user-tmpfiles-rules";
+
+  meta = with lib.maintainers; {
+    maintainers = [ schnusch ];
+  };
+
+  nodes.machine = { ... }: {
+    users.users = {
+      alice.isNormalUser = true;
+      bob.isNormalUser = true;
+    };
+
+    systemd.user.tmpfiles = {
+      rules = [
+        "d %h/user_tmpfiles_created"
+      ];
+      users.alice.rules = [
+        "d %h/only_alice"
+      ];
+    };
+  };
+
+  testScript = { ... }: ''
+    machine.succeed("loginctl enable-linger alice bob")
+
+    machine.wait_until_succeeds("systemctl --user --machine=alice@ is-active systemd-tmpfiles-setup.service")
+    machine.succeed("[ -d ~alice/user_tmpfiles_created ]")
+    machine.succeed("[ -d ~alice/only_alice ]")
+
+    machine.wait_until_succeeds("systemctl --user --machine=bob@ is-active systemd-tmpfiles-setup.service")
+    machine.succeed("[ -d ~bob/user_tmpfiles_created ]")
+    machine.succeed("[ ! -e ~bob/only_alice ]")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd-userdbd.nix b/nixpkgs/nixos/tests/systemd-userdbd.nix
new file mode 100644
index 000000000000..5d0233ffd9fb
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd-userdbd.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "systemd-userdbd";
+  nodes.machine = { config, pkgs, ... }: {
+    services.userdbd.enable = true;
+
+    users.users.test-user-nss = {
+      isNormalUser = true;
+    };
+
+    environment.etc."userdb/test-user-dropin.user".text = builtins.toJSON {
+      userName = "test-user-dropin";
+    };
+
+    environment.systemPackages = with pkgs; [ libvarlink ];
+  };
+  testScript = ''
+    import json
+    from shlex import quote
+
+    def getUserRecord(name):
+      Interface = "unix:/run/systemd/userdb/io.systemd.Multiplexer/io.systemd.UserDatabase"
+      payload = json.dumps({
+        "service": "io.systemd.Multiplexer",
+        "userName": name
+      })
+      return json.loads(machine.succeed(f"varlink call {Interface}.GetUserRecord {quote(payload)}"))
+
+    machine.wait_for_unit("systemd-userdbd.socket")
+    getUserRecord("test-user-nss")
+    getUserRecord("test-user-dropin")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/systemd.nix b/nixpkgs/nixos/tests/systemd.nix
new file mode 100644
index 000000000000..1a39cc73c886
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemd.nix
@@ -0,0 +1,205 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "systemd";
+
+  nodes.machine = { lib, ... }: {
+    imports = [ common/user-account.nix common/x11.nix ];
+
+    virtualisation.emptyDiskImages = [ 512 512 ];
+
+    environment.systemPackages = [ pkgs.cryptsetup ];
+
+    virtualisation.fileSystems = {
+      "/test-x-initrd-mount" = {
+        device = "/dev/vdb";
+        fsType = "ext2";
+        autoFormat = true;
+        noCheck = true;
+        options = [ "x-initrd.mount" ];
+      };
+    };
+
+    systemd.extraConfig = "DefaultEnvironment=\"XXX_SYSTEM=foo\"";
+    systemd.user.extraConfig = "DefaultEnvironment=\"XXX_USER=bar\"";
+    services.journald.extraConfig = "Storage=volatile";
+    test-support.displayManager.auto.user = "alice";
+
+    systemd.shutdown.test = pkgs.writeScript "test.shutdown" ''
+      #!${pkgs.runtimeShell}
+      PATH=${lib.makeBinPath (with pkgs; [ util-linux coreutils ])}
+      mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared
+      touch /tmp/shared/shutdown-test
+      umount /tmp/shared
+    '';
+
+    systemd.services.oncalendar-test = {
+      description = "calendar test";
+      # Japan does not have DST which makes the test a little bit simpler
+      startAt = "Wed 10:00 Asia/Tokyo";
+      script = "true";
+    };
+
+    systemd.services.testservice1 = {
+      description = "Test Service 1";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        if [ "$XXX_SYSTEM" = foo ]; then
+          touch /system_conf_read
+        fi
+      '';
+    };
+
+    systemd.user.services.testservice2 = {
+      description = "Test Service 2";
+      wantedBy = [ "default.target" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        if [ "$XXX_USER" = bar ]; then
+          touch "$HOME/user_conf_read"
+        fi
+      '';
+    };
+
+    systemd.watchdog = {
+      device = "/dev/watchdog";
+      runtimeTime = "30s";
+      rebootTime = "10min";
+      kexecTime = "5min";
+    };
+  };
+
+  testScript = ''
+    import re
+    import subprocess
+
+    machine.wait_for_x()
+    # wait for user services
+    machine.wait_for_unit("default.target", "alice")
+
+    with subtest("systemctl edit suggests --runtime"):
+        # --runtime is suggested when using `systemctl edit`
+        ret, out = machine.execute("systemctl edit testservice1.service 2>&1")
+        assert ret == 1
+        assert out.rstrip("\n") == "The unit-directory '/etc/systemd/system' is read-only on NixOS, so it's not possible to edit system-units directly. Use 'systemctl edit --runtime' instead."
+        # editing w/o `--runtime` is possible for user-services, however
+        # it's not possible because we're not in a tty when grepping
+        # (i.e. hacky way to ensure that the error from above doesn't appear here).
+        _, out = machine.execute("systemctl --user edit testservice2.service 2>&1")
+        assert out.rstrip("\n") == "Cannot edit units if not on a tty."
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/105049
+    with subtest("systemd reads timezone database in /etc/zoneinfo"):
+        timer = machine.succeed("TZ=UTC systemctl show --property=TimersCalendar oncalendar-test.timer")
+        assert re.search("next_elapse=Wed ....-..-.. 01:00:00 UTC", timer), f"got {timer.strip()}"
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/35415
+    with subtest("configuration files are recognized by systemd"):
+        machine.succeed("test -e /system_conf_read")
+        machine.succeed("test -e /home/alice/user_conf_read")
+        machine.succeed("test -z $(ls -1 /var/log/journal)")
+
+    with subtest("regression test for https://bugs.freedesktop.org/show_bug.cgi?id=77507"):
+        retcode, output = machine.execute("systemctl status testservice1.service")
+        assert retcode in [0, 3]  # https://bugs.freedesktop.org/show_bug.cgi?id=77507
+
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/35268
+    with subtest("file system with x-initrd.mount is not unmounted"):
+        machine.succeed("mountpoint -q /test-x-initrd-mount")
+        machine.shutdown()
+
+        subprocess.check_call(
+            [
+                "qemu-img",
+                "convert",
+                "-O",
+                "raw",
+                "vm-state-machine/empty0.qcow2",
+                "x-initrd-mount.raw",
+            ]
+        )
+        extinfo = subprocess.check_output(
+            [
+                "${pkgs.e2fsprogs}/bin/dumpe2fs",
+                "x-initrd-mount.raw",
+            ]
+        ).decode("utf-8")
+        assert (
+            re.search(r"^Filesystem state: *clean$", extinfo, re.MULTILINE) is not None
+        ), ("File system was not cleanly unmounted: " + extinfo)
+
+    # Regression test for https://github.com/NixOS/nixpkgs/pull/91232
+    with subtest("setting transient hostnames works"):
+        machine.succeed("hostnamectl set-hostname --transient machine-transient")
+        machine.fail("hostnamectl set-hostname machine-all")
+
+    with subtest("systemd-shutdown works"):
+        machine.shutdown()
+        machine.wait_for_unit("multi-user.target")
+        machine.succeed("test -e /tmp/shared/shutdown-test")
+
+    # Test settings from /etc/sysctl.d/50-default.conf are applied
+    with subtest("systemd sysctl settings are applied"):
+        machine.wait_for_unit("multi-user.target")
+        assert "fq_codel" in machine.succeed("sysctl net.core.default_qdisc")
+
+    # Test systemd is configured to manage a watchdog
+    with subtest("systemd manages hardware watchdog"):
+        machine.wait_for_unit("multi-user.target")
+
+        # It seems that the device's path doesn't appear in 'systemctl show' so
+        # check it separately.
+        assert "WatchdogDevice=/dev/watchdog" in machine.succeed(
+            "cat /etc/systemd/system.conf"
+        )
+
+        output = machine.succeed("systemctl show | grep Watchdog")
+        # assert "RuntimeWatchdogUSec=30s" in output
+        # for some reason RuntimeWatchdogUSec, doesn't seem to be updated in here.
+        assert "RebootWatchdogUSec=10min" in output
+        assert "KExecWatchdogUSec=5min" in output
+
+    # Test systemd cryptsetup support
+    with subtest("systemd successfully reads /etc/crypttab and unlocks volumes"):
+        # create a luks volume and put a filesystem on it
+        machine.succeed(
+            "echo -n supersecret | cryptsetup luksFormat -q /dev/vdc -",
+            "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vdc foo",
+            "mkfs.ext3 /dev/mapper/foo",
+        )
+
+        # create a keyfile and /etc/crypttab
+        machine.succeed("echo -n supersecret > /var/lib/luks-keyfile")
+        machine.succeed("chmod 600 /var/lib/luks-keyfile")
+        machine.succeed("echo 'luks1 /dev/vdc /var/lib/luks-keyfile luks' > /etc/crypttab")
+
+        # after a reboot, systemd should unlock the volume and we should be able to mount it
+        machine.shutdown()
+        machine.succeed("systemctl status systemd-cryptsetup@luks1.service")
+        machine.succeed("mkdir -p /tmp/luks1")
+        machine.succeed("mount /dev/mapper/luks1 /tmp/luks1")
+
+    # Do some IP traffic
+    output_ping = machine.succeed(
+        "systemd-run --wait -- ping -c 1 127.0.0.1 2>&1"
+    )
+
+    with subtest("systemd reports accounting data on system.slice"):
+        output = machine.succeed("systemctl status system.slice")
+        assert "CPU:" in output
+        assert "Memory:" in output
+
+        assert "IP:" in output
+        assert "0B in, 0B out" not in output
+
+        assert "IO:" in output
+        assert "0B read, 0B written" not in output
+
+    with subtest("systemd per-unit accounting works"):
+        assert "IP traffic received: 84B" in output_ping
+        assert "IP traffic sent: 84B" in output_ping
+
+    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/nixpkgs/nixos/tests/systemtap.nix b/nixpkgs/nixos/tests/systemtap.nix
new file mode 100644
index 000000000000..5cd79d66e872
--- /dev/null
+++ b/nixpkgs/nixos/tests/systemtap.nix
@@ -0,0 +1,50 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}@args:
+
+with pkgs.lib;
+
+let
+  stapScript = pkgs.writeText "test.stp" ''
+    probe kernel.function("do_sys_poll") {
+      println("kernel function probe & println work")
+      exit()
+    }
+  '';
+
+  ## TODO shared infra with ../kernel-generic.nix
+  testsForLinuxPackages = linuxPackages: (import ./make-test-python.nix ({ pkgs, ... }: {
+    name = "kernel-${linuxPackages.kernel.version}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bendlas ];
+    };
+
+    nodes.machine = { ... }:
+      {
+        boot.kernelPackages = linuxPackages;
+        programs.systemtap.enable = true;
+      };
+
+    testScript =
+      ''
+        with subtest("Capture stap ouput"):
+            output = machine.succeed("stap ${stapScript} 2>&1")
+
+        with subtest("Ensure that expected output from stap script is there"):
+            assert "kernel function probe & println work\n" == output, "kernel function probe & println work\n != " + output
+      '';
+  }) args);
+
+  ## TODO shared infra with ../kernel-generic.nix
+  kernels = {
+    inherit (pkgs.linuxKernel.packageAliases) linux_default linux_latest;
+  };
+
+in mapAttrs (_: lP: testsForLinuxPackages lP) kernels // {
+  passthru = {
+    inherit testsForLinuxPackages;
+
+    testsForKernel = kernel: testsForLinuxPackages (pkgs.linuxPackagesFor kernel);
+  };
+}
diff --git a/nixpkgs/nixos/tests/tandoor-recipes.nix b/nixpkgs/nixos/tests/tandoor-recipes.nix
new file mode 100644
index 000000000000..18beaac6f062
--- /dev/null
+++ b/nixpkgs/nixos/tests/tandoor-recipes.nix
@@ -0,0 +1,41 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "tandoor-recipes";
+  meta.maintainers = with lib.maintainers; [ ambroisie ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.tandoor-recipes = {
+      enable = true;
+      extraConfig = {
+        DB_ENGINE = "django.db.backends.postgresql";
+        POSTGRES_HOST = "/run/postgresql";
+        POSTGRES_USER = "tandoor_recipes";
+        POSTGRES_DB = "tandoor_recipes";
+      };
+    };
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "tandoor_recipes" ];
+      ensureUsers = [
+        {
+          name = "tandoor_recipes";
+          ensureDBOwnership = true;
+        }
+      ];
+    };
+
+    systemd.services = {
+      tandoor-recipes = {
+        after = [ "postgresql.service" ];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("tandoor-recipes.service")
+
+    with subtest("Web interface gets ready"):
+        # Wait until server accepts connections
+        machine.wait_until_succeeds("curl -fs localhost:8080")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tang.nix b/nixpkgs/nixos/tests/tang.nix
new file mode 100644
index 000000000000..10486a9feb8c
--- /dev/null
+++ b/nixpkgs/nixos/tests/tang.nix
@@ -0,0 +1,81 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "tang";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ jfroche ];
+  };
+
+  nodes.server =
+    { config
+    , pkgs
+    , modulesPath
+    , ...
+    }: {
+      imports = [
+        "${modulesPath}/../tests/common/auto-format-root-device.nix"
+      ];
+      virtualisation = {
+        emptyDiskImages = [ 512 ];
+        useBootLoader = true;
+        useEFIBoot = true;
+        # This requires to have access
+        # to a host Nix store as
+        # the new root device is /dev/vdb
+        # an empty 512MiB drive, containing no Nix store.
+        mountHostNixStore = true;
+      };
+
+      boot.loader.systemd-boot.enable = true;
+
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "192.168.0.1"; prefixLength = 24; }
+      ];
+
+      environment.systemPackages = with pkgs; [ clevis tang cryptsetup ];
+      services.tang = {
+        enable = true;
+        ipAddressAllow = [ "127.0.0.1/32" ];
+      };
+    };
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("sockets.target")
+
+    with subtest("Check keys are generated"):
+      machine.wait_until_succeeds("curl -v http://127.0.0.1:7654/adv")
+      key = machine.wait_until_succeeds("tang-show-keys 7654")
+
+    with subtest("Check systemd access list"):
+      machine.succeed("ping -c 3 192.168.0.1")
+      machine.fail("curl -v --connect-timeout 3 http://192.168.0.1:7654/adv")
+
+    with subtest("Check basic encrypt and decrypt message"):
+      machine.wait_until_succeeds(f"""echo 'Hello World' | clevis encrypt tang '{{ "url": "http://127.0.0.1:7654", "thp":"{key}"}}' > /tmp/encrypted""")
+      decrypted = machine.wait_until_succeeds("clevis decrypt < /tmp/encrypted")
+      assert decrypted.strip() == "Hello World"
+      machine.wait_until_succeeds("tang-show-keys 7654")
+
+    with subtest("Check encrypt and decrypt disk"):
+      machine.succeed("cryptsetup luksFormat --force-password --batch-mode /dev/vdb <<<'password'")
+      machine.succeed(f"""clevis luks bind -s1 -y -f -d /dev/vdb tang '{{ "url": "http://127.0.0.1:7654", "thp":"{key}" }}' <<< 'password' """)
+      clevis_luks = machine.succeed("clevis luks list -d /dev/vdb")
+      assert clevis_luks.strip() == """1: tang '{"url":"http://127.0.0.1:7654"}'"""
+      machine.succeed("clevis luks unlock -d /dev/vdb")
+      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")
+      machine.succeed("clevis luks unlock -d /dev/vdb")
+      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")
+      # without tang available, unlock should fail
+      machine.succeed("systemctl stop tangd.socket")
+      machine.fail("clevis luks unlock -d /dev/vdb")
+      machine.succeed("systemctl start tangd.socket")
+
+    with subtest("Rotate server keys"):
+      machine.succeed("${pkgs.tang}/libexec/tangd-rotate-keys -d /var/lib/tang")
+      machine.succeed("clevis luks unlock -d /dev/vdb")
+      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")
+
+    with subtest("Test systemd service security"):
+        output = machine.succeed("systemd-analyze security tangd@.service")
+        machine.log(output)
+        assert output[-9:-1] == "SAFE :-}"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/taskserver.nix b/nixpkgs/nixos/tests/taskserver.nix
new file mode 100644
index 000000000000..254bc8822f89
--- /dev/null
+++ b/nixpkgs/nixos/tests/taskserver.nix
@@ -0,0 +1,275 @@
+import ./make-test-python.nix ({ pkgs, ... }: let
+  snakeOil = pkgs.runCommand "snakeoil-certs" {
+    outputs = [ "out" "cacert" "cert" "key" "crl" ];
+    buildInputs = [ pkgs.gnutls.bin ];
+    caTemplate = pkgs.writeText "snakeoil-ca.template" ''
+      cn = server
+      expiration_days = -1
+      cert_signing_key
+      ca
+    '';
+    certTemplate = pkgs.writeText "snakeoil-cert.template" ''
+      cn = server
+      expiration_days = -1
+      tls_www_server
+      encryption_key
+      signing_key
+    '';
+    crlTemplate = pkgs.writeText "snakeoil-crl.template" ''
+      expiration_days = -1
+    '';
+    userCertTemplate = pkgs.writeText "snakeoil-user-cert.template" ''
+      organization = snakeoil
+      cn = server
+      expiration_days = -1
+      tls_www_client
+      encryption_key
+      signing_key
+    '';
+  } ''
+    certtool -p --bits 4096 --outfile ca.key
+    certtool -s --template "$caTemplate" --load-privkey ca.key \
+                --outfile "$cacert"
+    certtool -p --bits 4096 --outfile "$key"
+    certtool -c --template "$certTemplate" \
+                --load-ca-privkey ca.key \
+                --load-ca-certificate "$cacert" \
+                --load-privkey "$key" \
+                --outfile "$cert"
+    certtool --generate-crl --template "$crlTemplate" \
+                            --load-ca-privkey ca.key \
+                            --load-ca-certificate "$cacert" \
+                            --outfile "$crl"
+
+    mkdir "$out"
+
+    # Stripping key information before the actual PEM-encoded values is solely
+    # to make test output a bit less verbose when copying the client key to the
+    # actual client.
+    certtool -p --bits 4096 | sed -n \
+      -e '/^----* *BEGIN/,/^----* *END/p' > "$out/alice.key"
+
+    certtool -c --template "$userCertTemplate" \
+                --load-privkey "$out/alice.key" \
+                --load-ca-privkey ca.key \
+                --load-ca-certificate "$cacert" \
+                --outfile "$out/alice.cert"
+  '';
+
+in {
+  name = "taskserver";
+
+  nodes = rec {
+    server = {
+      services.taskserver.enable = true;
+      services.taskserver.listenHost = "::";
+      services.taskserver.openFirewall = true;
+      services.taskserver.fqdn = "server";
+      services.taskserver.organisations = {
+        testOrganisation.users = [ "alice" "foo" ];
+        anotherOrganisation.users = [ "bob" ];
+      };
+
+      specialisation.manual-config.configuration = {
+        services.taskserver.pki.manual = {
+          ca.cert = snakeOil.cacert;
+          server.cert = snakeOil.cert;
+          server.key = snakeOil.key;
+          server.crl = snakeOil.crl;
+        };
+      };
+    };
+
+    client1 = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.taskwarrior pkgs.gnutls ];
+      users.users.alice.isNormalUser = true;
+      users.users.bob.isNormalUser = true;
+      users.users.foo.isNormalUser = true;
+      users.users.bar.isNormalUser = true;
+    };
+
+    client2 = client1;
+  };
+
+  testScript = { nodes, ... }: let
+    cfg = nodes.server.config.services.taskserver;
+    portStr = toString cfg.listenPort;
+    specialisations = "${nodes.server.system.build.toplevel}/specialisation";
+    newServerSystem = "${specialisations}/manual-config";
+    switchToNewServer = "${newServerSystem}/bin/switch-to-configuration test";
+  in ''
+    from shlex import quote
+
+
+    def su(user, cmd):
+        return f"su - {user} -c {quote(cmd)}"
+
+
+    def no_extra_init(client, org, user):
+        pass
+
+
+    def setup_clients_for(org, user, extra_init=no_extra_init):
+        for client in [client1, client2]:
+            with client.nested(f"initialize client for user {user}"):
+                client.succeed(
+                    su(user, f"rm -rf /home/{user}/.task"),
+                    su(user, "task rc.confirmation=no config confirmation no"),
+                )
+
+                exportinfo = server.succeed(f"nixos-taskserver user export {org} {user}")
+
+                with client.nested("importing taskwarrior configuration"):
+                    client.succeed(su(user, f"eval {quote(exportinfo)} >&2"))
+
+                extra_init(client, org, user)
+
+                client.succeed(su(user, "task config taskd.server server:${portStr} >&2"))
+
+                client.succeed(su(user, "task sync init >&2"))
+
+
+    def restart_server():
+        server.systemctl("restart taskserver.service")
+        server.wait_for_open_port(${portStr})
+
+
+    def re_add_imperative_user():
+        with server.nested("(re-)add imperative user bar"):
+            server.execute("nixos-taskserver org remove imperativeOrg")
+            server.succeed(
+                "nixos-taskserver org add imperativeOrg",
+                "nixos-taskserver user add imperativeOrg bar",
+            )
+            setup_clients_for("imperativeOrg", "bar")
+
+
+    def test_sync(user):
+        with subtest(f"sync for user {user}"):
+            client1.succeed(su(user, "task add foo >&2"))
+            client1.succeed(su(user, "task sync >&2"))
+            client2.fail(su(user, "task list >&2"))
+            client2.succeed(su(user, "task sync >&2"))
+            client2.succeed(su(user, "task list >&2"))
+
+
+    def check_client_cert(user):
+        # debug level 3 is a workaround for gnutls issue https://gitlab.com/gnutls/gnutls/-/issues/1040
+        cmd = (
+            f"gnutls-cli -d 3"
+            f" --x509cafile=/home/{user}/.task/keys/ca.cert"
+            f" --x509keyfile=/home/{user}/.task/keys/private.key"
+            f" --x509certfile=/home/{user}/.task/keys/public.cert"
+            f" --port=${portStr} server < /dev/null"
+        )
+        return su(user, cmd)
+
+
+    # Explicitly start the VMs so that we don't accidentally start newServer
+    server.start()
+    client1.start()
+    client2.start()
+
+    server.wait_for_unit("taskserver.service")
+
+    server.succeed(
+        "nixos-taskserver user list testOrganisation | grep -qxF alice",
+        "nixos-taskserver user list testOrganisation | grep -qxF foo",
+        "nixos-taskserver user list anotherOrganisation | grep -qxF bob",
+    )
+
+    server.wait_for_open_port(${portStr})
+
+    client1.wait_for_unit("multi-user.target")
+    client2.wait_for_unit("multi-user.target")
+
+    setup_clients_for("testOrganisation", "alice")
+    setup_clients_for("testOrganisation", "foo")
+    setup_clients_for("anotherOrganisation", "bob")
+
+    for user in ["alice", "bob", "foo"]:
+        test_sync(user)
+
+    server.fail("nixos-taskserver user add imperativeOrg bar")
+    re_add_imperative_user()
+
+    test_sync("bar")
+
+    with subtest("checking certificate revocation of user bar"):
+        client1.succeed(check_client_cert("bar"))
+
+        server.succeed("nixos-taskserver user remove imperativeOrg bar")
+        restart_server()
+
+        client1.fail(check_client_cert("bar"))
+
+        client1.succeed(su("bar", "task add destroy everything >&2"))
+        client1.fail(su("bar", "task sync >&2"))
+
+    re_add_imperative_user()
+
+    with subtest("checking certificate revocation of org imperativeOrg"):
+        client1.succeed(check_client_cert("bar"))
+
+        server.succeed("nixos-taskserver org remove imperativeOrg")
+        restart_server()
+
+        client1.fail(check_client_cert("bar"))
+
+        client1.succeed(su("bar", "task add destroy even more >&2"))
+        client1.fail(su("bar", "task sync >&2"))
+
+    re_add_imperative_user()
+
+    with subtest("check whether declarative config overrides user bar"):
+        restart_server()
+        test_sync("bar")
+
+
+    def init_manual_config(client, org, user):
+        cfgpath = f"/home/{user}/.task"
+
+        client.copy_from_host(
+            "${snakeOil.cacert}",
+            f"{cfgpath}/ca.cert",
+        )
+        for file in ["alice.key", "alice.cert"]:
+            client.copy_from_host(
+                f"${snakeOil}/{file}",
+                f"{cfgpath}/{file}",
+            )
+
+        for file in [f"{user}.key", f"{user}.cert"]:
+            client.copy_from_host(
+                f"${snakeOil}/{file}",
+                f"{cfgpath}/{file}",
+            )
+
+        client.succeed(
+            su("alice", f"task config taskd.ca {cfgpath}/ca.cert"),
+            su("alice", f"task config taskd.key {cfgpath}/{user}.key"),
+            su(user, f"task config taskd.certificate {cfgpath}/{user}.cert"),
+        )
+
+
+    with subtest("check manual configuration"):
+        # Remove the keys from automatic CA creation, to make sure the new
+        # generation doesn't use keys from before.
+        server.succeed("rm -rf ${cfg.dataDir}/keys/* >&2")
+
+        server.succeed(
+            "${switchToNewServer} >&2"
+        )
+        server.wait_for_unit("taskserver.service")
+        server.wait_for_open_port(${portStr})
+
+        server.succeed(
+            "nixos-taskserver org add manualOrg",
+            "nixos-taskserver user add manualOrg alice",
+        )
+
+        setup_clients_for("manualOrg", "alice", init_manual_config)
+
+        test_sync("alice")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tayga.nix b/nixpkgs/nixos/tests/tayga.nix
new file mode 100644
index 000000000000..4aade67d74d0
--- /dev/null
+++ b/nixpkgs/nixos/tests/tayga.nix
@@ -0,0 +1,237 @@
+# This test verifies that we can ping an IPv4-only server from an IPv6-only
+# client via a NAT64 router. The hosts and networks are configured as follows:
+#
+#        +------
+# Client | eth1    Address: 2001:db8::2/64
+#        |  |      Route:   64:ff9b::/96 via 2001:db8::1
+#        +--|---
+#           | VLAN 3
+#        +--|---
+#        | eth2    Address: 2001:db8::1/64
+# Router |
+#        | nat64   Address: 64:ff9b::1/128
+#        |         Route:   64:ff9b::/96
+#        |         Address: 192.0.2.0/32
+#        |         Route:   192.0.2.0/24
+#        |
+#        | eth1    Address: 100.64.0.1/24
+#        +--|---
+#           | VLAN 2
+#        +--|---
+# Server | eth1    Address: 100.64.0.2/24
+#        |         Route:   192.0.2.0/24 via 100.64.0.1
+#        +------
+
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "tayga";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hax404 ];
+  };
+
+  nodes = {
+    # The server is configured with static IPv4 addresses. RFC 6052 Section 3.1
+    # disallows the mapping of non-global IPv4 addresses like RFC 1918 into the
+    # Well-Known Prefix 64:ff9b::/96. TAYGA also does not allow the mapping of
+    # documentation space (RFC 5737). To circumvent this, 100.64.0.2/24 from
+    # RFC 6589 (Carrier Grade NAT) is used here.
+    # To reach the IPv4 address pool of the NAT64 gateway, there is a static
+    # route configured. In normal cases, where the router would also source NAT
+    # the pool addresses to one IPv4 addresses, this would not be needed.
+    server = {
+      virtualisation.vlans = [
+        2 # towards router
+      ];
+      networking = {
+        useDHCP = false;
+        interfaces.eth1 = lib.mkForce {};
+      };
+      systemd.network = {
+        enable = true;
+        networks."vlan1" = {
+          matchConfig.Name = "eth1";
+          address = [
+            "100.64.0.2/24"
+          ];
+          routes = [
+            { routeConfig = { Destination = "192.0.2.0/24"; Gateway = "100.64.0.1"; }; }
+          ];
+        };
+      };
+    };
+
+    # The router is configured with static IPv4 addresses towards the server
+    # and IPv6 addresses towards the client. For NAT64, the Well-Known prefix
+    # 64:ff9b::/96 is used. NAT64 is done with TAYGA which provides the
+    # tun-interface nat64 and does the translation over it. The IPv6 packets
+    # are sent to this interfaces and received as IPv4 packets and vice versa.
+    # As TAYGA only translates IPv6 addresses to dedicated IPv4 addresses, it
+    # needs a pool of IPv4 addresses which must be at least as big as the
+    # expected amount of clients. In this test, the packets from the pool are
+    # directly routed towards the client. In normal cases, there would be a
+    # second source NAT44 to map all clients behind one IPv4 address.
+    router_systemd = {
+      boot.kernel.sysctl = {
+        "net.ipv4.ip_forward" = 1;
+        "net.ipv6.conf.all.forwarding" = 1;
+      };
+
+      virtualisation.vlans = [
+        2 # towards server
+        3 # towards client
+      ];
+
+      networking = {
+        useDHCP = false;
+        useNetworkd = true;
+        firewall.enable = false;
+        interfaces.eth1 = lib.mkForce {
+          ipv4 = {
+            addresses = [ { address = "100.64.0.1"; prefixLength = 24; } ];
+          };
+        };
+        interfaces.eth2 = lib.mkForce {
+          ipv6 = {
+            addresses = [ { address = "2001:db8::1"; prefixLength = 64; } ];
+          };
+        };
+      };
+
+      services.tayga = {
+        enable = true;
+        ipv4 = {
+          address = "192.0.2.0";
+          router = {
+            address = "192.0.2.1";
+          };
+          pool = {
+            address = "192.0.2.0";
+            prefixLength = 24;
+          };
+        };
+        ipv6 = {
+          address = "2001:db8::1";
+          router = {
+            address = "64:ff9b::1";
+          };
+          pool = {
+            address = "64:ff9b::";
+            prefixLength = 96;
+          };
+        };
+      };
+    };
+
+    router_nixos = {
+      boot.kernel.sysctl = {
+        "net.ipv4.ip_forward" = 1;
+        "net.ipv6.conf.all.forwarding" = 1;
+      };
+
+      virtualisation.vlans = [
+        2 # towards server
+        3 # towards client
+      ];
+
+      networking = {
+        useDHCP = false;
+        firewall.enable = false;
+        interfaces.eth1 = lib.mkForce {
+          ipv4 = {
+            addresses = [ { address = "100.64.0.1"; prefixLength = 24; } ];
+          };
+        };
+        interfaces.eth2 = lib.mkForce {
+          ipv6 = {
+            addresses = [ { address = "2001:db8::1"; prefixLength = 64; } ];
+          };
+        };
+      };
+
+      services.tayga = {
+        enable = true;
+        ipv4 = {
+          address = "192.0.2.0";
+          router = {
+            address = "192.0.2.1";
+          };
+          pool = {
+            address = "192.0.2.0";
+            prefixLength = 24;
+          };
+        };
+        ipv6 = {
+          address = "2001:db8::1";
+          router = {
+            address = "64:ff9b::1";
+          };
+          pool = {
+            address = "64:ff9b::";
+            prefixLength = 96;
+          };
+        };
+      };
+    };
+
+    # The client is configured with static IPv6 addresses. It has also a static
+    # route for the NAT64 IP space where the IPv4 addresses are mapped in. In
+    # normal cases, there would be only a default route.
+    client = {
+      virtualisation.vlans = [
+        3 # towards router
+      ];
+
+      networking = {
+        useDHCP = false;
+        interfaces.eth1 = lib.mkForce {};
+      };
+
+      systemd.network = {
+        enable = true;
+        networks."vlan1" = {
+          matchConfig.Name = "eth1";
+          address = [
+            "2001:db8::2/64"
+          ];
+          routes = [
+            { routeConfig = { Destination = "64:ff9b::/96"; Gateway = "2001:db8::1"; }; }
+          ];
+        };
+      };
+      environment.systemPackages = [ pkgs.mtr ];
+    };
+  };
+
+  testScript = ''
+    # start client and server
+    for machine in client, server:
+      machine.systemctl("start network-online.target")
+      machine.wait_for_unit("network-online.target")
+      machine.log(machine.execute("ip addr")[1])
+      machine.log(machine.execute("ip route")[1])
+      machine.log(machine.execute("ip -6 route")[1])
+
+    # test systemd-networkd and nixos-scripts based router
+    for router in router_systemd, router_nixos:
+      router.start()
+      router.systemctl("start network-online.target")
+      router.wait_for_unit("network-online.target")
+      router.wait_for_unit("tayga.service")
+      router.log(machine.execute("ip addr")[1])
+      router.log(machine.execute("ip route")[1])
+      router.log(machine.execute("ip -6 route")[1])
+
+      with subtest("Wait for tayga"):
+        router.wait_for_unit("tayga.service")
+
+      with subtest("Test ICMP"):
+        client.wait_until_succeeds("ping -c 3 64:ff9b::100.64.0.2 >&2")
+
+      with subtest("Test ICMP and show a traceroute"):
+        client.wait_until_succeeds("mtr --show-ips --report-wide 64:ff9b::100.64.0.2 >&2")
+
+      router.log(router.execute("systemd-analyze security tayga.service")[1])
+      router.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/teeworlds.nix b/nixpkgs/nixos/tests/teeworlds.nix
new file mode 100644
index 000000000000..ac2c996955c8
--- /dev/null
+++ b/nixpkgs/nixos/tests/teeworlds.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  client =
+    { pkgs, ... }:
+
+    { imports = [ ./common/x11.nix ];
+      environment.systemPackages = [ pkgs.teeworlds ];
+    };
+
+in {
+  name = "teeworlds";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ hax404 ];
+  };
+
+  nodes =
+    { server =
+      { services.teeworlds = {
+          enable = true;
+          openPorts = true;
+        };
+      };
+
+      client1 = client;
+      client2 = client;
+    };
+
+    testScript =
+    ''
+      start_all()
+
+      server.wait_for_unit("teeworlds.service")
+      server.wait_until_succeeds("ss --numeric --udp --listening | grep -q 8303")
+
+      client1.wait_for_x()
+      client2.wait_for_x()
+
+      client1.execute("teeworlds 'player_name Alice;connect server' >&2 &")
+      server.wait_until_succeeds(
+          'journalctl -u teeworlds -e | grep --extended-regexp -q "team_join player=\'[0-9]:Alice"'
+      )
+
+      client2.execute("teeworlds 'player_name Bob;connect server' >&2 &")
+      server.wait_until_succeeds(
+          'journalctl -u teeworlds -e | grep --extended-regexp -q "team_join player=\'[0-9]:Bob"'
+      )
+
+      server.sleep(10)  # wait for a while to get a nice screenshot
+
+      client1.screenshot("screen_client1")
+      client2.screenshot("screen_client2")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/telegraf.nix b/nixpkgs/nixos/tests/telegraf.nix
new file mode 100644
index 000000000000..c3cdb1645213
--- /dev/null
+++ b/nixpkgs/nixos/tests/telegraf.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "telegraf";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mic92 ];
+  };
+
+  nodes.machine = { ... }: {
+    services.telegraf.enable = true;
+    services.telegraf.environmentFiles = [(pkgs.writeText "secrets" ''
+      SECRET=example
+    '')];
+    services.telegraf.extraConfig = {
+      agent.interval = "1s";
+      agent.flush_interval = "1s";
+      inputs.exec = {
+        commands = [
+          "${pkgs.runtimeShell} -c 'echo $SECRET,tag=a i=42i'"
+        ];
+        timeout = "5s";
+        data_format = "influx";
+      };
+      outputs.file.files = ["/tmp/metrics.out"];
+      outputs.file.data_format = "influx";
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("telegraf.service")
+    machine.wait_until_succeeds("grep -q example /tmp/metrics.out")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/teleport.nix b/nixpkgs/nixos/tests/teleport.nix
new file mode 100644
index 000000000000..d68917c6c7ac
--- /dev/null
+++ b/nixpkgs/nixos/tests/teleport.nix
@@ -0,0 +1,116 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  packages = with pkgs; {
+    "default" = teleport;
+    "12" = teleport_12;
+    "13" = teleport_13;
+  };
+
+  minimal = package: {
+    services.teleport = {
+      enable = true;
+      inherit package;
+    };
+  };
+
+  client = package: {
+    services.teleport = {
+      enable = true;
+      inherit package;
+      settings = {
+        teleport = {
+          nodename = "client";
+          advertise_ip = "192.168.1.20";
+          auth_token = "8d1957b2-2ded-40e6-8297-d48156a898a9";
+          auth_servers = [ "192.168.1.10:3025" ];
+          log.severity = "DEBUG";
+        };
+        ssh_service = {
+          enabled = true;
+          labels = {
+            role = "client";
+          };
+        };
+        proxy_service.enabled = false;
+        auth_service.enabled = false;
+      };
+    };
+    networking.interfaces.eth1.ipv4.addresses = [{
+      address = "192.168.1.20";
+      prefixLength = 24;
+    }];
+  };
+
+  server = package: {
+    services.teleport = {
+      enable = true;
+      inherit package;
+      settings = {
+        teleport = {
+          nodename = "server";
+          advertise_ip = "192.168.1.10";
+        };
+        ssh_service.enabled = true;
+        proxy_service.enabled = true;
+        auth_service = {
+          enabled = true;
+          tokens = [ "node:8d1957b2-2ded-40e6-8297-d48156a898a9" ];
+        };
+      };
+      diag.enable = true;
+      insecure.enable = true;
+    };
+    networking = {
+      firewall.allowedTCPPorts = [ 3025 ];
+      interfaces.eth1.ipv4.addresses = [{
+        address = "192.168.1.10";
+        prefixLength = 24;
+      }];
+    };
+  };
+in
+lib.concatMapAttrs
+  (name: package: {
+    "minimal_${name}" = makeTest {
+      # minimal setup should always work
+      name = "teleport-minimal-setup";
+      meta.maintainers = with pkgs.lib.maintainers; [ justinas ];
+      nodes.minimal = minimal package;
+
+      testScript = ''
+        minimal.wait_for_open_port(3025)
+        minimal.wait_for_open_port(3080)
+        minimal.wait_for_open_port(3022)
+      '';
+    };
+
+    "basic_${name}" = makeTest {
+      # basic server and client test
+      name = "teleport-server-client";
+      meta.maintainers = with pkgs.lib.maintainers; [ justinas ];
+      nodes = {
+        server = server package;
+        client = client package;
+      };
+
+      testScript = ''
+        with subtest("teleport ready"):
+            server.wait_for_open_port(3025)
+            client.wait_for_open_port(3022)
+
+        with subtest("check applied configuration"):
+            server.wait_until_succeeds("tctl get nodes --format=json | ${pkgs.jq}/bin/jq -e '.[] | select(.spec.hostname==\"client\") | .metadata.labels.role==\"client\"'")
+            server.wait_for_open_port(3000)
+            client.succeed("journalctl -u teleport.service --grep='DEBU'")
+            server.succeed("journalctl -u teleport.service --grep='Starting teleport in insecure mode.'")
+      '';
+    };
+  })
+  packages
diff --git a/nixpkgs/nixos/tests/terminal-emulators.nix b/nixpkgs/nixos/tests/terminal-emulators.nix
new file mode 100644
index 000000000000..3c1188ca88c9
--- /dev/null
+++ b/nixpkgs/nixos/tests/terminal-emulators.nix
@@ -0,0 +1,217 @@
+# Terminal emulators all present a pretty similar interface.
+# That gives us an opportunity to easily test their basic functionality with a single codebase.
+#
+# There are two tests run on each terminal emulator
+# - can it successfully execute a command passed on the cmdline?
+# - can it successfully display a colour?
+# the latter is used as a proxy for "can it display text?", without going through all the intricacies of OCR.
+#
+# 256-colour terminal mode is used to display the test colour, since it has a universally-applicable palette (unlike 8- and 16- colour, where the colours are implementation-defined), and it is widely supported (unlike 24-bit colour).
+#
+# Future work:
+# - Wayland support (both for testing the existing terminals, and for testing wayland-only terminals like foot and havoc)
+# - Test keyboard input? (skipped for now, to eliminate the possibility of race conditions and focus issues)
+
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let tests = {
+      alacritty.pkg = p: p.alacritty;
+
+      contour.pkg = p: p.contour;
+      contour.cmd = "contour early-exit-threshold 0 execute $command";
+
+      cool-retro-term.pkg = p: p.cool-retro-term;
+      cool-retro-term.colourTest = false; # broken by gloss effect
+
+      ctx.pkg = p: p.ctx;
+      ctx.pinkValue = "#FE0065";
+
+      darktile.pkg = p: p.darktile;
+
+      deepin-terminal.pkg = p: p.deepin.deepin-terminal;
+
+      eterm.pkg = p: p.eterm;
+      eterm.executable = "Eterm";
+      eterm.pinkValue = "#D40055";
+
+      germinal.pkg = p: p.germinal;
+
+      gnome-terminal.pkg = p: p.gnome.gnome-terminal;
+
+      guake.pkg = p: p.guake;
+      guake.cmd = "SHELL=$command guake --show";
+      guake.kill = true;
+
+      hyper.pkg = p: p.hyper;
+
+      kermit.pkg = p: p.kermit-terminal;
+
+      kgx.pkg = p: p.kgx;
+      kgx.cmd = "kgx -e $command";
+      kgx.kill = true;
+
+      kitty.pkg = p: p.kitty;
+      kitty.cmd = "kitty $command";
+
+      konsole.pkg = p: p.plasma5Packages.konsole;
+
+      lomiri-terminal-app.pkg = p: p.lomiri.lomiri-terminal-app;
+
+      lxterminal.pkg = p: p.lxterminal;
+
+      mate-terminal.pkg = p: p.mate.mate-terminal;
+      mate-terminal.cmd = "SHELL=$command mate-terminal --disable-factory"; # factory mode uses dbus, and we don't have a proper dbus session set up
+
+      mlterm.pkg = p: p.mlterm;
+
+      mrxvt.pkg = p: p.mrxvt;
+
+      qterminal.pkg = p: p.lxqt.qterminal;
+      qterminal.kill = true;
+
+      rio.pkg = p: p.rio;
+      rio.cmd = "rio -e $command";
+      rio.colourTest = false; # the rendering is changing too much so colors change every release.
+
+      roxterm.pkg = p: p.roxterm;
+      roxterm.cmd = "roxterm -e $command";
+
+      sakura.pkg = p: p.sakura;
+
+      st.pkg = p: p.st;
+      st.kill = true;
+
+      stupidterm.pkg = p: p.stupidterm;
+      stupidterm.cmd = "stupidterm -- $command";
+
+      terminator.pkg = p: p.terminator;
+      terminator.cmd = "terminator -e $command";
+
+      terminology.pkg = p: p.enlightenment.terminology;
+      terminology.cmd = "SHELL=$command terminology --no-wizard=true";
+      terminology.colourTest = false; # broken by gloss effect
+
+      termite.pkg = p: p.termite;
+
+      termonad.pkg = p: p.termonad;
+
+      tilda.pkg = p: p.tilda;
+
+      tilix.pkg = p: p.tilix;
+      tilix.cmd = "tilix -e $command";
+
+      urxvt.pkg = p: p.rxvt-unicode;
+
+      wayst.pkg = p: p.wayst;
+      wayst.pinkValue = "#FF0066";
+
+      # times out after spending many hours
+      #wezterm.pkg = p: p.wezterm;
+
+      xfce4-terminal.pkg = p: p.xfce.xfce4-terminal;
+
+      xterm.pkg = p: p.xterm;
+    };
+in mapAttrs (name: { pkg, executable ? name, cmd ? "SHELL=$command ${executable}", colourTest ? true, pinkValue ? "#FF0087", kill ? false }: makeTest
+{
+  name = "terminal-emulator-${name}";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ jjjollyjim ];
+  };
+
+  nodes.machine = { pkgsInner, ... }:
+
+  {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+
+    # Hyper (and any other electron-based terminals) won't run as root
+    test-support.displayManager.auto.user = "alice";
+
+    environment.systemPackages = [
+      (pkg pkgs)
+      (pkgs.writeShellScriptBin "report-success" ''
+        echo 1 > /tmp/term-ran-successfully
+        ${optionalString kill "pkill ${executable}"}
+      '')
+      (pkgs.writeShellScriptBin "display-colour" ''
+        # A 256-colour background colour code for pink, then spaces.
+        #
+        # Background is used rather than foreground to minimize the effect of anti-aliasing.
+        #
+        # Keep adding more in case the window is partially offscreen to the left or requires
+        # a change to correctly redraw after initialising the window (as with ctx).
+
+        while :
+        do
+            echo -ne "\e[48;5;198m                   "
+            sleep 0.5
+        done
+        sleep infinity
+      '')
+      (pkgs.writeShellScriptBin "run-in-this-term" "sudo -u alice run-in-this-term-wrapped $1")
+
+      (pkgs.writeShellScriptBin "run-in-this-term-wrapped" "command=\"$(which \"$1\")\"; ${cmd}")
+    ];
+
+    # Helpful reminder to add this test to passthru.tests
+    warnings = if !((pkg pkgs) ? "passthru" && (pkg pkgs).passthru ? "tests") then [ "The package for ${name} doesn't have a passthru.tests" ] else [ ];
+  };
+
+  # We need imagemagick, though not tesseract
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+  in ''
+    with subtest("wait for x"):
+        start_all()
+        machine.wait_for_x()
+
+    with subtest("have the terminal run a command"):
+        # We run this command synchronously, so we can be certain the exit codes are happy
+        machine.${if kill then "execute" else "succeed"}("run-in-this-term report-success")
+        machine.wait_for_file("/tmp/term-ran-successfully")
+    ${optionalString colourTest ''
+
+    import tempfile
+    import subprocess
+
+
+    def check_for_pink(final=False) -> bool:
+        with tempfile.NamedTemporaryFile() as tmpin:
+            machine.send_monitor_command("screendump {}".format(tmpin.name))
+
+            cmd = 'convert {} -define histogram:unique-colors=true -format "%c" histogram:info:'.format(
+                tmpin.name
+            )
+            ret = subprocess.run(cmd, shell=True, capture_output=True)
+            if ret.returncode != 0:
+                raise Exception(
+                    "image analysis failed with exit code {}".format(ret.returncode)
+                )
+
+            text = ret.stdout.decode("utf-8")
+            return "${pinkValue}" in text
+
+
+    with subtest("ensuring no pink is present without the terminal"):
+        assert (
+            check_for_pink() == False
+        ), "Pink was present on the screen before we even launched a terminal!"
+
+    with subtest("have the terminal display a colour"):
+        # We run this command in the background
+        assert machine.shell is not None
+        machine.shell.send(b"(run-in-this-term display-colour |& systemd-cat -t terminal) &\n")
+
+        with machine.nested("Waiting for the screen to have pink on it:"):
+            retry(check_for_pink)
+  ''}'';
+}
+
+  ) tests
diff --git a/nixpkgs/nixos/tests/thelounge.nix b/nixpkgs/nixos/tests/thelounge.nix
new file mode 100644
index 000000000000..8d5a37d46c46
--- /dev/null
+++ b/nixpkgs/nixos/tests/thelounge.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix {
+  name = "thelounge";
+
+  nodes = {
+    private = { config, pkgs, ... }: {
+      services.thelounge = {
+        enable = true;
+        plugins = [ pkgs.theLoungePlugins.themes.solarized ];
+      };
+    };
+
+    public = { config, pkgs, ... }: {
+      services.thelounge = {
+        enable = true;
+        public = true;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    for machine in machines:
+      machine.wait_for_unit("thelounge.service")
+      machine.wait_for_open_port(9000)
+
+    private.wait_until_succeeds("journalctl -u thelounge.service | grep thelounge-theme-solarized")
+    private.wait_until_succeeds("journalctl -u thelounge.service | grep 'in private mode'")
+    public.wait_until_succeeds("journalctl -u thelounge.service | grep 'in public mode'")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/tiddlywiki.nix b/nixpkgs/nixos/tests/tiddlywiki.nix
new file mode 100644
index 000000000000..822711b8939c
--- /dev/null
+++ b/nixpkgs/nixos/tests/tiddlywiki.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "tiddlywiki";
+  nodes = {
+    default = {
+      services.tiddlywiki.enable = true;
+    };
+    configured = {
+      boot.postBootCommands = ''
+        echo "username,password
+        somelogin,somesecret" > /var/lib/wikiusers.csv
+      '';
+      services.tiddlywiki = {
+        enable = true;
+        listenOptions = {
+          port = 3000;
+          credentials="../wikiusers.csv";
+          readers="(authenticated)";
+        };
+      };
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+
+      with subtest("by default works without configuration"):
+          default.wait_for_unit("tiddlywiki.service")
+
+      with subtest("by default available on port 8080 without auth"):
+          default.wait_for_unit("tiddlywiki.service")
+          default.wait_for_open_port(8080)
+          # we output to /dev/null here to avoid a python UTF-8 decode error
+          # but the check will still fail if the service doesn't respond
+          default.succeed("curl --fail -o /dev/null 127.0.0.1:8080")
+
+      with subtest("by default creates empty wiki"):
+          default.succeed("test -f /var/lib/tiddlywiki/tiddlywiki.info")
+
+      with subtest("configured on port 3000 with basic auth"):
+          configured.wait_for_unit("tiddlywiki.service")
+          configured.wait_for_open_port(3000)
+          configured.fail("curl --fail -o /dev/null 127.0.0.1:3000")
+          configured.succeed(
+              "curl --fail -o /dev/null 127.0.0.1:3000 --user somelogin:somesecret"
+          )
+
+      with subtest("restart preserves changes"):
+          # given running wiki
+          default.wait_for_unit("tiddlywiki.service")
+          # with some changes
+          default.succeed(
+              'curl --fail --request PUT --header \'X-Requested-With:TiddlyWiki\' \
+              --data \'{ "title": "title", "text": "content" }\' \
+              --url 127.0.0.1:8080/recipes/default/tiddlers/somepage '
+          )
+          default.succeed("sleep 2")
+
+          # when wiki is cycled
+          default.systemctl("restart tiddlywiki.service")
+          default.wait_for_unit("tiddlywiki.service")
+          default.wait_for_open_port(8080)
+
+          # the change is preserved
+          default.succeed(
+              "curl --fail -o /dev/null 127.0.0.1:8080/recipes/default/tiddlers/somepage"
+          )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/tigervnc.nix b/nixpkgs/nixos/tests/tigervnc.nix
new file mode 100644
index 000000000000..ed575682d933
--- /dev/null
+++ b/nixpkgs/nixos/tests/tigervnc.nix
@@ -0,0 +1,53 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+makeTest {
+  name = "tigervnc";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lheckemann ];
+  };
+
+  nodes = {
+    server = { pkgs, ...}: {
+      environment.systemPackages = with pkgs; [
+        tigervnc # for Xvnc
+        xorg.xwininfo
+        imagemagickBig # for display with working label: support
+      ];
+      networking.firewall.allowedTCPPorts = [ 5901 ];
+    };
+
+    client = { pkgs, ... }: {
+      imports = [ ./common/x11.nix ];
+      # for vncviewer
+      environment.systemPackages = [ pkgs.tigervnc ];
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    start_all()
+
+    for host in [server, client]:
+        host.succeed("echo foobar | vncpasswd -f > vncpasswd")
+
+    server.succeed("Xvnc -geometry 720x576 :1 -PasswordFile vncpasswd >&2 &")
+    server.wait_until_succeeds("nc -z localhost 5901", timeout=10)
+    server.succeed("DISPLAY=:1 xwininfo -root | grep 720x576")
+    server.execute("DISPLAY=:1 display -size 360x200 -font sans -gravity south label:'HELLO VNC WORLD' >&2 &")
+
+    client.wait_for_x()
+    client.execute("vncviewer server:1 -PasswordFile vncpasswd >&2 &")
+    client.wait_for_window(r"VNC")
+    client.screenshot("screenshot")
+    text = client.get_screen_text()
+    # Displayed text
+    assert 'HELLO VNC WORLD' in text
+    # Client window title
+    assert 'TigerVNC' in text
+  '';
+}
diff --git a/nixpkgs/nixos/tests/timescaledb.nix b/nixpkgs/nixos/tests/timescaledb.nix
new file mode 100644
index 000000000000..ba0a3cec6076
--- /dev/null
+++ b/nixpkgs/nixos/tests/timescaledb.nix
@@ -0,0 +1,93 @@
+# mostly copied from ./postgresql.nix as it seemed unapproriate to
+# test additional extensions for postgresql there.
+
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE EXTENSION timescaledb;
+    CREATE EXTENSION timescaledb_toolkit;
+
+    CREATE TABLE sth (
+      time TIMESTAMPTZ NOT NULL,
+      value DOUBLE PRECISION
+    );
+
+    SELECT create_hypertable('sth', 'time');
+
+    INSERT INTO sth (time, value) VALUES
+    ('2003-04-12 04:05:06 America/New_York', 1.0),
+    ('2003-04-12 04:05:07 America/New_York', 2.0),
+    ('2003-04-12 04:05:08 America/New_York', 3.0),
+    ('2003-04-12 04:05:09 America/New_York', 4.0),
+    ('2003-04-12 04:05:10 America/New_York', 5.0)
+    ;
+
+    WITH t AS (
+      SELECT
+        time_bucket('1 day'::interval, time) AS dt,
+        stats_agg(value) AS stats
+      FROM sth
+      GROUP BY time_bucket('1 day'::interval, time)
+    )
+    SELECT
+      average(stats)
+    FROM t;
+  '';
+  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ typetetris ];
+    };
+
+    nodes.machine = { ... }:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          extraPlugins = ps: with ps; [
+            timescaledb
+            timescaledb_toolkit
+          ];
+          settings = { shared_preload_libraries = "timescaledb, timescaledb_toolkit"; };
+        };
+      };
+
+    testScript = ''
+      def check_count(statement, lines):
+          return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format(
+              statement, lines
+          )
+
+
+      machine.start()
+      machine.wait_for_unit("postgresql")
+
+      with subtest("Postgresql with extensions timescaledb and timescaledb_toolkit is available just after unit start"):
+          machine.succeed(
+              "sudo -u postgres psql -f ${test-sql}"
+          )
+
+      machine.fail(check_count("SELECT * FROM sth;", 3))
+      machine.succeed(check_count("SELECT * FROM sth;", 5))
+      machine.fail(check_count("SELECT * FROM sth;", 4))
+
+      machine.shutdown()
+    '';
+
+  };
+  applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12") postgresql-versions;
+in
+mapAttrs'
+  (name: package: {
+    inherit name;
+    value = make-postgresql-test name package;
+  })
+  applicablePostgresqlVersions
diff --git a/nixpkgs/nixos/tests/timezone.nix b/nixpkgs/nixos/tests/timezone.nix
new file mode 100644
index 000000000000..7fc9a5058eee
--- /dev/null
+++ b/nixpkgs/nixos/tests/timezone.nix
@@ -0,0 +1,50 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "timezone";
+  meta.maintainers = with pkgs.lib.maintainers; [ lheckemann ];
+
+  nodes = {
+    node_eutz = { pkgs, ... }: {
+      time.timeZone = "Europe/Amsterdam";
+    };
+
+    node_nulltz = { pkgs, ... }: {
+      time.timeZone = null;
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+      node_eutz.wait_for_unit("dbus.socket")
+
+      with subtest("static - Ensure timezone change gives the correct result"):
+          node_eutz.fail("timedatectl set-timezone Asia/Tokyo")
+          date_result = node_eutz.succeed('date -d @0 "+%Y-%m-%d %H:%M:%S"')
+          assert date_result == "1970-01-01 01:00:00\n", "Timezone seems to be wrong"
+
+      node_nulltz.wait_for_unit("dbus.socket")
+
+      with subtest("imperative - Ensure timezone defaults to UTC"):
+          date_result = node_nulltz.succeed('date -d @0 "+%Y-%m-%d %H:%M:%S"')
+          print(date_result)
+          assert (
+              date_result == "1970-01-01 00:00:00\n"
+          ), "Timezone seems to be wrong (not UTC)"
+
+      with subtest("imperative - Ensure timezone adjustment produces expected result"):
+          node_nulltz.succeed("timedatectl set-timezone Asia/Tokyo")
+
+          # Adjustment should be taken into account
+          date_result = node_nulltz.succeed('date -d @0 "+%Y-%m-%d %H:%M:%S"')
+          print(date_result)
+          assert date_result == "1970-01-01 09:00:00\n", "Timezone was not adjusted"
+
+      with subtest("imperative - Ensure timezone adjustment persists across reboot"):
+          # Adjustment should persist across a reboot
+          node_nulltz.shutdown()
+          node_nulltz.wait_for_unit("dbus.socket")
+          date_result = node_nulltz.succeed('date -d @0 "+%Y-%m-%d %H:%M:%S"')
+          print(date_result)
+          assert (
+              date_result == "1970-01-01 09:00:00\n"
+          ), "Timezone adjustment was not persisted"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tinc/default.nix b/nixpkgs/nixos/tests/tinc/default.nix
new file mode 100644
index 000000000000..31b675ad35c0
--- /dev/null
+++ b/nixpkgs/nixos/tests/tinc/default.nix
@@ -0,0 +1,139 @@
+import ../make-test-python.nix ({ lib, ... }:
+  let
+    snakeoil-keys = import ./snakeoil-keys.nix;
+
+    hosts = lib.attrNames snakeoil-keys;
+
+    subnetOf = name: config:
+      let
+        subnets = config.services.tinc.networks.myNetwork.hostSettings.${name}.subnets;
+      in
+      (builtins.head subnets).address;
+
+    makeTincHost = name: { subnet, extraConfig ? { } }: lib.mkMerge [
+      {
+        subnets = [{ address = subnet; }];
+        settings = {
+          Ed25519PublicKey = snakeoil-keys.${name}.ed25519Public;
+        };
+        rsaPublicKey = snakeoil-keys.${name}.rsaPublic;
+      }
+      extraConfig
+    ];
+
+    makeTincNode = { config, ... }: name: extraConfig: lib.mkMerge [
+      {
+        services.tinc.networks.myNetwork = {
+          inherit name;
+          rsaPrivateKeyFile =
+            builtins.toFile "rsa.priv" snakeoil-keys.${name}.rsaPrivate;
+          ed25519PrivateKeyFile =
+            builtins.toFile "ed25519.priv" snakeoil-keys.${name}.ed25519Private;
+
+          hostSettings = lib.mapAttrs makeTincHost {
+            static = {
+              subnet = "10.0.0.11";
+              # Only specify the addresses in the node's vlans, Tinc does not
+              # seem to try each one, unlike the documentation suggests...
+              extraConfig.addresses = map
+                (vlan: { address = "192.168.${toString vlan}.11"; port = 655; })
+                config.virtualisation.vlans;
+            };
+            dynamic1 = { subnet = "10.0.0.21"; };
+            dynamic2 = { subnet = "10.0.0.22"; };
+          };
+        };
+
+        networking.useDHCP = false;
+
+        networking.interfaces."tinc.myNetwork" = {
+          virtual = true;
+          virtualType = "tun";
+          ipv4.addresses = [{
+            address = subnetOf name config;
+            prefixLength = 24;
+          }];
+        };
+
+        # Prevents race condition between NixOS service and tinc creating the
+        # interface.
+        # See: https://github.com/NixOS/nixpkgs/issues/27070
+        systemd.services."tinc.myNetwork" = {
+          after = [ "network-addresses-tinc.myNetwork.service" ];
+          requires = [ "network-addresses-tinc.myNetwork.service" ];
+        };
+
+        networking.firewall.allowedTCPPorts = [ 655 ];
+        networking.firewall.allowedUDPPorts = [ 655 ];
+      }
+      extraConfig
+    ];
+
+  in
+  {
+    name = "tinc";
+    meta.maintainers = with lib.maintainers; [ minijackson ];
+
+    nodes = {
+
+      static = { ... } @ args:
+        makeTincNode args "static" {
+          virtualisation.vlans = [ 1 2 ];
+
+          networking.interfaces.eth1.ipv4.addresses = [{
+            address = "192.168.1.11";
+            prefixLength = 24;
+          }];
+
+          networking.interfaces.eth2.ipv4.addresses = [{
+            address = "192.168.2.11";
+            prefixLength = 24;
+          }];
+        };
+
+
+      dynamic1 = { ... } @ args:
+        makeTincNode args "dynamic1" {
+          virtualisation.vlans = [ 1 ];
+        };
+
+      dynamic2 = { ... } @ args:
+        makeTincNode args "dynamic2" {
+          virtualisation.vlans = [ 2 ];
+        };
+
+    };
+
+    testScript = ''
+      start_all()
+
+      static.wait_for_unit("tinc.myNetwork.service")
+      dynamic1.wait_for_unit("tinc.myNetwork.service")
+      dynamic2.wait_for_unit("tinc.myNetwork.service")
+
+      # Static is accessible by the other hosts
+      dynamic1.succeed("ping -c5 192.168.1.11")
+      dynamic2.succeed("ping -c5 192.168.2.11")
+
+      # The other hosts are in separate vlans
+      dynamic1.fail("ping -c5 192.168.2.11")
+      dynamic2.fail("ping -c5 192.168.1.11")
+
+      # Each host can ping themselves through Tinc
+      static.succeed("ping -c5 10.0.0.11")
+      dynamic1.succeed("ping -c5 10.0.0.21")
+      dynamic2.succeed("ping -c5 10.0.0.22")
+
+      # Static is accessible by the other hosts through Tinc
+      dynamic1.succeed("ping -c5 10.0.0.11")
+      dynamic2.succeed("ping -c5 10.0.0.11")
+
+      # Static can access the other hosts through Tinc
+      static.succeed("ping -c5 10.0.0.21")
+      static.succeed("ping -c5 10.0.0.22")
+
+      # The other hosts in separate vlans can access each other through Tinc
+      dynamic1.succeed("ping -c5 10.0.0.22")
+      dynamic2.succeed("ping -c5 10.0.0.21")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/tinc/snakeoil-keys.nix b/nixpkgs/nixos/tests/tinc/snakeoil-keys.nix
new file mode 100644
index 000000000000..650e57d61d4a
--- /dev/null
+++ b/nixpkgs/nixos/tests/tinc/snakeoil-keys.nix
@@ -0,0 +1,157 @@
+{
+  static = {
+    ed25519Private = ''
+      -----BEGIN ED25519 PRIVATE KEY-----
+      IPR+ur5LfVdm6VlR1+FGIkbkL8Enkb9sejBa/JP6tXkg/vHoraIp70srb6jAUFm5
+      3YbCJiBjLW3dy16qM5PovBoWtr5hoqYYA9dFLOys8FBUFFsIGfKhnbk7g25iwxbO
+      -----END ED25519 PRIVATE KEY-----
+    '';
+
+    ed25519Public = "AqV7aeIqKGGQfXxijMLfRAVRBLixnS45G5OoduIc8mD";
+
+    rsaPrivate = ''
+      -----BEGIN RSA PRIVATE KEY-----
+      MIIEpAIBAAKCAQEAxDHl0TIhhT2yH5rT+Q7MLnj+Ir8bbs3uaPqnzcxWzN1EfVP8
+      TWt5fSTrF2Dc78Kyu5ZNALrp7tUj0GZAegp1YeYJ28p3qTwCveywtCwbB4dI987S
+      yJwq95kE9aoyLa+cT99VwSTdb2YowQv2tWj/idxE3oJ+qZjy9tE5mysXm7jmTQDx
+      +U0XmNe6MHjKXc01Ener41u0ykJLeUfdgJ1zEyM2rQGtaHpIXfMT6kmxCaMcAMLg
+      YFpI38/1pQGQtROKdGOaUomx2m058bkMsJhTiBjESiLRDElRGxmMJ732crGJP0GR
+      ChJkaX/CnxHq7R0daZfwoTVHRu6N7WDbFQL5twIDAQABAoIBAQCM/fLTIHyYXRr5
+      vXFhxXGUYBz56W6UdWdEiAU5TwR92vFSQ53IIVlARtyvg0ui/b8mMcAKq0hb+03u
+      gN0LFyL+BKvHCLxvoRGzXTorcJrIET+t3jL6OchjANNgnDvNOytQ9wWQdKaxXLAi
+      8y8LdXZWozXW1d6ikKjiGL+WNCSWIcq83ktSJZcohihptU9Un16FYQzdolSC8RtI
+      XyT7i1ye6hW/wJTJxqZ4taX3EPat85kXS234VGSqg9bb2A1yE+U8Rq37bf8AKldJ
+      NUQB3JyxnkYGJcqvzDmz139+744VWxDRvXDA5vU29LC6f8bGBvwEttD98QW+pgmB
+      1NBU1Uo5AoGBAOzUk6k74h1RarwXaftjh/9Pures0CfNNnrkJApzFCh4bAoHNxq6
+      SSXqLcc/vvX2+YaZ72nn5YTo+JLQP6evM9oUaqRMAxa3nzoNCtF8U2r48UWmoUQE
+      aZCYbD3m7IVWFacCKRVaVTMZMTTicypSnXcbCSIEH8PRs9+L4jkHgql9AoGBANQT
+      TZECVhIaQnyRiKWlUE8G1QKzXIxjmfyirBe+ftlIG2XMXasAtQ4VRxpnorgqUnIH
+      BVrIbvRx21zlqwZbrZvyb1jHWRoyi1cqBPijpYBUm5LbV2jgHPhnfhRVqdD4CDKj
+      NQzIQrNymFaMWAoOQv/DE3g+Txr0fm9Ztu8ZRXZDAoGAHh3SQT0aPfwyhIS9t3gq
+      vS7YYa8aMVWJTgthAessbxERPB06xq1Vy/qBo8rZb9HeXV2J8n/I0iQGKDVPQvWm
+      tF7QSOBZrDPhjbJG4+jZesr5c5ADBfFBs1+OtDh/b11JF5nQu6RnHT5g4YbCemlT
+      GOhZOvgnSfGK3CyfsfzggskCgYEAmpKDK5kPUNxw70hH16v5L9Bj+zbt0qlZ+Ag8
+      9IV1ATuMNJNTBitay6v4iidVM3QtaUzyuytxq5s87qW7FMRHcm2ueH+70ttaMiq/
+      OtZT74g7aDuUpy0KEIemHn4dauENYJMSPIHOE+sHW7WpCZNBhBcUHsUTdSsU6GX0
+      bqr1tO8CgYBpZdR2OoX/rn8nwjmtBOH38aPnCpaAfdI2Eq2Lg6DjksP6TBt53a+R
+      m1lk6Kt37BPPZQ85SBr7ywvDgUzfoD7uSmHujF2JUHPsdrg9nx7pNIGlW6DlS9OU
+      oNXGAJ/6/y6F8uDbToUfrwFq5tKMypEEa32kFtxb9f0XQ5fSgHrBEw==
+      -----END RSA PRIVATE KEY-----
+    '';
+
+    rsaPublic = ''
+      -----BEGIN RSA PUBLIC KEY-----
+      MIIBCgKCAQEAxDHl0TIhhT2yH5rT+Q7MLnj+Ir8bbs3uaPqnzcxWzN1EfVP8TWt5
+      fSTrF2Dc78Kyu5ZNALrp7tUj0GZAegp1YeYJ28p3qTwCveywtCwbB4dI987SyJwq
+      95kE9aoyLa+cT99VwSTdb2YowQv2tWj/idxE3oJ+qZjy9tE5mysXm7jmTQDx+U0X
+      mNe6MHjKXc01Ener41u0ykJLeUfdgJ1zEyM2rQGtaHpIXfMT6kmxCaMcAMLgYFpI
+      38/1pQGQtROKdGOaUomx2m058bkMsJhTiBjESiLRDElRGxmMJ732crGJP0GRChJk
+      aX/CnxHq7R0daZfwoTVHRu6N7WDbFQL5twIDAQAB
+      -----END RSA PUBLIC KEY-----
+    '';
+  };
+
+  dynamic1 = {
+    ed25519Private = ''
+      -----BEGIN ED25519 PRIVATE KEY-----
+      wHNC2IMXfYtL4ehdsCX154HBvlIZYEiTOnXtckWMUtEAiX9fu7peyBkp9q+yOy9c
+      xsNyssLL78lt0GoweCxlu3Sza2oBQAcwb+6tuv7P/bqzcG005uCwquyCz8LVymXA
+      -----END ED25519 PRIVATE KEY-----
+    '';
+
+    ed25519Public = "t0smNaAEAH8mver77+z/m6MnBNdurAsqrswM/Sls5FA";
+
+    rsaPrivate = ''
+      -----BEGIN RSA PRIVATE KEY-----
+      MIIEpAIBAAKCAQEApukYNGFNWvVlmx75LyOE7MEcd/ViV+yEyk+4cIBXYJ3Ouw+/
+      oEuh8ghQfsiUtbUPR6hPYhX2ZV8XGhuU2nAXVQV0sfZ8pdkbHQ6wHUqFcUIQAVvS
+      Wpm2DvZM8jkbCPP64/x5nukPwQ8VoNnb62rWGzbcj7rOeb7ndMK0TpX5Wwv8F297
+      nKTNCEDbK3DLTj3VD+QGnw6AoEt5i44vViAWZBXuHLHWTDC0Nq8GG+9TKODkEwt5
+      4dgN2X9f+WTVAYhZT3SayHLqIFIMQunN89RpWwhHSW+JIRfAfuT1TbP+wA5ptDeI
+      ktCkJwWyv4hK6l800BJ9GW1nbId5LPa58ipaVwIDAQABAoIBAHcw3WgKVAMwWm57
+      n9ZZtwKapInFYYUIEYungj5UaBFGn+pVRLJjUDJWXaUr94YK1e6F8qpIpLufPBAY
+      wiN7CC5exwaOzlRgxUvqwTkpjkFiu6s8tuqb+baVjD0tKnEqSW+lS/R+2hEzhG5p
+      JPLoSB0HAFpjPC8UdJSctcWos3if3mvOGkGCKyTkrwaJgECDfD+lZ+NBIAiYLSps
+      jWLE+XlY1+nfPdLUQ+TRSv3IikJ/CWbvJLl9EE1tKhkY564KytwZrkIdJlc7NyRO
+      HpzhyMzHu1GLsr+OsBZByNNUxEPU+bzkDQluRXUSIUs9zZoBiCQr3o04qGPTEX9n
+      pNU60gECgYEA3Uf+c80eqzjDxv+O0YzC+9x6A+yMrV56siGkKRPMlrSqjX7iE2Yg
+      tUjD25kEvtaFuB3f/7zp3h4O/VLZgXreRtXHvdrfoyyJGHvHIyCGm8sw8CEWsKo4
+      1LgZUzdPJRkXJq1zOgS0r1xsA1UDC4s02Ww2HwNeVWtmLUyCpA+B/ccCgYEAwRk9
+      tbe82eq1a85zZiPVXP2qvDH5+Vz9YiMky8xsBnoxmz2siR+NdvWBLcE2VDIY8MK1
+      9a1dz2a7cAHQBrtWtACFVY4zvr69DumApjbQRClDYpJ42tp2VbzlMcUDIoKudRQV
+      CObhrE4w4yfVizXFyH9+4Tsg5NzVYuGg9fUJ/vECgYEAoRz7KouNqfMhsLF/5hkM
+      Gt9zw4mm/9ALm8kcwn/U9WHD0FQy/Rbd98BsQmaOavi80cqGvqhoyz2tgkqhbUHt
+      tzuOPDCxphgWFcqBupTDDYoLLruYzraRvGfyoIFj0coL7jBZ9kNY31l2l5J9LhmE
+      OE4utbP5Kk6RTagocpWL+x8CgYB48CwcIcWf3kZeDOFtuUeqhB1o3Qwox7rSuhwT
+      oCaQL/vdtNTY1PAu7zhGxdoXBYFlWS3JfxlgCoGedyQo8zAscJ8RpIx4DNIwAsLW
+      V0I9TnKry/zxZR30OOh7MV7zQFGvdjJubtwspJQt0QcHt1f2aRO4UOYbMMxcr9+1
+      7BCkoQKBgQDBEtg1hx9zYGg1WN2TBSvh6NShi9S23r6IZ3Up8vz6Z2rcwB3UuhKi
+      xluI2ZFwM9s+7UOpaGC+hnc1aMHDEguYOPXoIzvebbYAdN4AkrsJ5d0r1GoEe64E
+      UXxrfuv5LeJ/vkUgWof+U3/jGOVvrjzi5y1xOC0r3kiSpMa85s1dhQ==
+      -----END RSA PRIVATE KEY-----
+    '';
+
+    rsaPublic = ''
+      -----BEGIN RSA PUBLIC KEY-----
+      MIIBCgKCAQEApukYNGFNWvVlmx75LyOE7MEcd/ViV+yEyk+4cIBXYJ3Ouw+/oEuh
+      8ghQfsiUtbUPR6hPYhX2ZV8XGhuU2nAXVQV0sfZ8pdkbHQ6wHUqFcUIQAVvSWpm2
+      DvZM8jkbCPP64/x5nukPwQ8VoNnb62rWGzbcj7rOeb7ndMK0TpX5Wwv8F297nKTN
+      CEDbK3DLTj3VD+QGnw6AoEt5i44vViAWZBXuHLHWTDC0Nq8GG+9TKODkEwt54dgN
+      2X9f+WTVAYhZT3SayHLqIFIMQunN89RpWwhHSW+JIRfAfuT1TbP+wA5ptDeIktCk
+      JwWyv4hK6l800BJ9GW1nbId5LPa58ipaVwIDAQAB
+      -----END RSA PUBLIC KEY-----
+    '';
+  };
+
+  dynamic2 = {
+    ed25519Private = ''
+      -----BEGIN ED25519 PRIVATE KEY-----
+      oUx9JdIstZLMj3ZPD8mP3ITsUscCTIXhNF3VKFUVi/ma5uk50/1vrEohfDraiMxj
+      gAWthpkhnFzUbp+YlOHE7/Z3h1a/br2/tk8DoZ5PV6ufoV1MaBlGdu+TZgeZou0t
+      -----END ED25519 PRIVATE KEY-----
+    '';
+
+    ed25519Public = "f2dYt2/2q9fLJ/AaW+Tlu7HaVNjWQpRnr/UGoXGqLdL";
+
+    rsaPrivate = ''
+      -----BEGIN RSA PRIVATE KEY-----
+      MIIEpAIBAAKCAQEAtQfijPX3BwOAs2Y0EuNjcBmsI90uYqNAonrFgTtcVwERIVE6
+      p6alSEakazhByujBg3jI8oPKC8eO0IJ7x/BWcgxqaw8hsPfJZFnRlwEcU5kK4c+j
+      UNS+hJOXp0x97T1edLpSFHDK9bZ2necblHKG5MsI4UsxEa+CZ0yoIybwWCDmYuya
+      PvE7CeNNa+CIOUbtPVoN4p/aBj0vZeerNBBuodNkglKRxj4l9wD9uOx4S9sdK5lu
+      q/rkxlViBoXRAshT+G2d/u/7/WPoiKB3QJcF33z8UfrlsTRnDDqOMSGisTPSv2LK
+      4QLN4hWOGXAYQqZcxTkvvjl62mCDuoy0TM+CKQIDAQABAoIBAFKpMAxXf52nPswr
+      /dkmFVCpmE2kADsv+iJ21tpkpYxgw1aoRZUp5cyz3P3MaVZio4IJ1A/Ql6B7Vb3l
+      5ulr170p6CnMdgDdlAsLbEV8T1foyOxFKHiPPBNDZXsR1WpPnGLGdRY6TqKV12HQ
+      lmpZRTkRcJOXBufhcTUD7r5mWFaUoZ7so6VxR4L4Tzcgv1Rl4S6jgnHOQdO6lj47
+      BaPjpBb+hplJ4wsRm91dQ7JApYq25XZwyxnBwQ2zAwb46wsuFxDPHlSc4wU7qTt6
+      x2omm33Xy2cm8L1XQhrassZzldSnAyaLBh9DC3+vFPLODDxdz5M2kpHujYYctRhv
+      CICMYJUCgYEA7mWVYuw0S8FNjaLx6n9Q1hr9d9vAFDd3NEaegH586xvhYNxf6n+C
+      2zZloVLEsX0UnBU/6ZtLAUfxUIqlvDS2r1VjSYG5SNxM6/vyGl17Niu1jC8nzf7M
+      V1WtDCHhT4ikZCuNkAldtgI7CXVdCVO/fTqVhjk4hDblJo7VsCZSZysCgYEAwmXp
+      TwlDHapDqA8UxClZuxS8k+2hthny3ihRPCuT34yqAz074zYG97ZBKwIa4Lm1vnkc
+      mwU7yR2aK7IYeU4ScfWm1mLjkW5iaNV/sG7iTz/RP4mBAs3KSGmuhhz8sFWcXByU
+      IZyvMJvC+FpgJQJn/Xc8ZmdImvXlZd6k8v4/kfsCgYEA6VzFPB2OH63slb4w42SX
+      o86t2dtiDigxZxnN5GhtLdSP7borpigF10JLf/y+kCOpvhRLCQk8Bdf/z+C41iAf
+      yEhktbrnvfvwzHxHhSmHCAMHZ19trodCTiePCrZLkQhoK6o6nAmfEyDh26NoXE3/
+      v71OSyLOQRZfgDwHz7PjrBsCgYAe0zojpjxWP+FqjLmmQUhROgCNFGlIDuVMBOic
+      uexAznVG/ja42KBSNzwuLa9FYy1Gfr3idvn78g24UA1BbvfNyj4iUJv1O6OvK+uL
+      dom8N0pe4NbsMuWYhel+qqoG7AxXLtDuY4IEGy7XYr1MIQ2MS5PwSQBiUguGE7/k
+      KBy8cQKBgQCyC9R8VWJxQLqJxZGa9Ful01bSuntB5OLRfEjFCCuGiY/3Vj+mCiQL
+      GOfMOi2jrcnSNgUm0uevmiFCq9m7QiPiAcSYKXPWhsz/55jJIGcZy8bwyhZ2s2Mg
+      BGeZgj4RFORidqkt5g/KJz0+Wp6Ks4sLoCvOzkpeXvLzFVyzGkihrw==
+      -----END RSA PRIVATE KEY-----
+    '';
+
+    rsaPublic = ''
+      -----BEGIN RSA PUBLIC KEY-----
+      MIIBCgKCAQEAtQfijPX3BwOAs2Y0EuNjcBmsI90uYqNAonrFgTtcVwERIVE6p6al
+      SEakazhByujBg3jI8oPKC8eO0IJ7x/BWcgxqaw8hsPfJZFnRlwEcU5kK4c+jUNS+
+      hJOXp0x97T1edLpSFHDK9bZ2necblHKG5MsI4UsxEa+CZ0yoIybwWCDmYuyaPvE7
+      CeNNa+CIOUbtPVoN4p/aBj0vZeerNBBuodNkglKRxj4l9wD9uOx4S9sdK5luq/rk
+      xlViBoXRAshT+G2d/u/7/WPoiKB3QJcF33z8UfrlsTRnDDqOMSGisTPSv2LK4QLN
+      4hWOGXAYQqZcxTkvvjl62mCDuoy0TM+CKQIDAQAB
+      -----END RSA PUBLIC KEY-----
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/tinydns.nix b/nixpkgs/nixos/tests/tinydns.nix
new file mode 100644
index 000000000000..124508bc004b
--- /dev/null
+++ b/nixpkgs/nixos/tests/tinydns.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ lib, ...} : {
+  name = "tinydns";
+  meta = {
+    maintainers = with lib.maintainers; [ basvandijk ];
+  };
+  nodes = {
+    nameserver = { config, lib, ... } : let
+      ip = (lib.head config.networking.interfaces.eth1.ipv4.addresses).address;
+    in {
+      networking.nameservers = [ ip ];
+      services.tinydns = {
+        enable = true;
+        inherit ip;
+        data = ''
+          .foo.bar:${ip}
+          +.bla.foo.bar:1.2.3.4:300
+        '';
+      };
+    };
+  };
+  testScript = ''
+    nameserver.start()
+    nameserver.wait_for_unit("tinydns.service")
+
+    # We query tinydns a few times to trigger the bug:
+    #
+    #   nameserver # [    6.105872] mmap: tinydns (842): VmData 331776 exceed data ulimit 300000. Update limits or use boot option ignore_rlimit_data.
+    #
+    # which was reported in https://github.com/NixOS/nixpkgs/issues/119066.
+    # Without the patch <nixpkgs/pkgs/tools/networking/djbdns/softlimit.patch>
+    # it fails on the 10th iteration.
+    nameserver.succeed(
+        """
+          for i in {1..100}; do
+            host bla.foo.bar 192.168.1.1 | grep '1\.2\.3\.4'
+          done
+        """
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tinyproxy.nix b/nixpkgs/nixos/tests/tinyproxy.nix
new file mode 100644
index 000000000000..b8448d4c23b6
--- /dev/null
+++ b/nixpkgs/nixos/tests/tinyproxy.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "tinyproxy";
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.tinyproxy = {
+      enable = true;
+      settings = {
+        Listen = "127.0.0.1";
+        Port = 8080;
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("tinyproxy.service")
+    machine.wait_for_open_port(8080)
+
+    machine.succeed('curl -s http://localhost:8080 |grep -i tinyproxy')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tinywl.nix b/nixpkgs/nixos/tests/tinywl.nix
new file mode 100644
index 000000000000..9199866b57af
--- /dev/null
+++ b/nixpkgs/nixos/tests/tinywl.nix
@@ -0,0 +1,59 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+  {
+    name = "tinywl";
+    meta = {
+      maintainers = with lib.maintainers; [ primeos ];
+    };
+
+    nodes.machine = { config, ... }: {
+      # Automatically login on tty1 as a normal user:
+      imports = [ ./common/user-account.nix ];
+      services.getty.autologinUser = "alice";
+      security.polkit.enable = true;
+
+      environment = {
+        systemPackages = with pkgs; [ tinywl foot wayland-utils ];
+      };
+
+      hardware.opengl.enable = true;
+
+      # Automatically start TinyWL when logging in on tty1:
+      programs.bash.loginShellInit = ''
+        if [ "$(tty)" = "/dev/tty1" ]; then
+          set -e
+          test ! -e /tmp/tinywl.log # Only start tinywl once
+          readonly TEST_CMD="wayland-info |& tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok; read"
+          readonly FOOT_CMD="foot sh -c '$TEST_CMD'"
+          tinywl -s "$FOOT_CMD" |& tee /tmp/tinywl.log
+          touch /tmp/tinywl-exit-ok
+        fi
+      '';
+
+      # Switch to a different GPU driver (default: -vga std), otherwise TinyWL segfaults:
+      virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+    };
+
+    testScript = { nodes, ... }: ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+
+      # Wait for complete startup:
+      machine.wait_until_succeeds("pgrep tinywl")
+      machine.wait_for_file("/run/user/1000/wayland-0")
+      machine.wait_until_succeeds("pgrep foot")
+      machine.wait_for_file("/tmp/test-wayland-exit-ok")
+
+      # Make a screenshot and save the result:
+      machine.screenshot("tinywl_foot")
+      print(machine.succeed("cat /tmp/test-wayland.out"))
+      machine.copy_from_vm("/tmp/test-wayland.out")
+
+      # Terminate cleanly:
+      machine.send_key("alt-esc")
+      machine.wait_until_fails("pgrep foot")
+      machine.wait_until_fails("pgrep tinywl")
+      machine.wait_for_file("/tmp/tinywl-exit-ok")
+      machine.copy_from_vm("/tmp/tinywl.log")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/tmate-ssh-server.nix b/nixpkgs/nixos/tests/tmate-ssh-server.nix
new file mode 100644
index 000000000000..122434c505c1
--- /dev/null
+++ b/nixpkgs/nixos/tests/tmate-ssh-server.nix
@@ -0,0 +1,74 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  inherit (import ./ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+
+  setUpPrivateKey = name: ''
+    ${name}.succeed(
+        "mkdir -p /root/.ssh",
+        "chown 700 /root/.ssh",
+        "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil",
+        "chown 600 /root/.ssh/id_snakeoil",
+    )
+    ${name}.wait_for_file("/root/.ssh/id_snakeoil")
+  '';
+
+  sshOpts = "-oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oIdentityFile=/root/.ssh/id_snakeoil";
+
+in
+{
+  name = "tmate-ssh-server";
+  nodes =
+    {
+      server = { ... }: {
+        services.tmate-ssh-server = {
+          enable = true;
+          port = 2223;
+          openFirewall = true;
+        };
+      };
+      client = { ... }: {
+        environment.systemPackages = [ pkgs.tmate ];
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+      };
+      client2 = { ... }: {
+        environment.systemPackages = [ pkgs.openssh ];
+      };
+    };
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("tmate-ssh-server.service")
+    server.wait_for_open_port(2223)
+    server.wait_for_file("/etc/tmate-ssh-server-keys/ssh_host_ed25519_key.pub")
+    server.wait_for_file("/etc/tmate-ssh-server-keys/ssh_host_rsa_key.pub")
+    server.succeed("tmate-client-config > /tmp/tmate.conf")
+    server.wait_for_file("/tmp/tmate.conf")
+
+    ${setUpPrivateKey "server"}
+    client.wait_for_unit("sshd.service")
+    client.wait_for_open_port(22)
+    server.succeed("scp ${sshOpts} /tmp/tmate.conf client:/tmp/tmate.conf")
+
+    client.wait_for_file("/tmp/tmate.conf")
+    client.send_chars("root\n")
+    client.sleep(2)
+    client.send_chars("tmate -f /tmp/tmate.conf\n")
+    client.sleep(2)
+    client.send_chars("q")
+    client.sleep(2)
+    client.send_chars("tmate display -p '#{tmate_ssh}' > /tmp/ssh_command\n")
+    client.wait_for_file("/tmp/ssh_command")
+    ssh_cmd = client.succeed("cat /tmp/ssh_command")
+
+    client2.succeed("mkdir -p ~/.ssh; ssh-keyscan -p 2223 server > ~/.ssh/known_hosts")
+    client2.send_chars("root\n")
+    client2.sleep(2)
+    client2.send_chars(ssh_cmd.strip() + "\n")
+    client2.sleep(2)
+    client2.send_chars("touch /tmp/client_2\n")
+
+    client.wait_for_file("/tmp/client_2")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tomcat.nix b/nixpkgs/nixos/tests/tomcat.nix
new file mode 100644
index 000000000000..df5cb033b78f
--- /dev/null
+++ b/nixpkgs/nixos/tests/tomcat.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "tomcat";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.tomcat = {
+      enable = true;
+      axis2.enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("tomcat.service")
+    machine.wait_for_open_port(8080)
+    machine.wait_for_file("/var/tomcat/webapps/examples");
+
+    machine.succeed(
+        "curl -sS --fail http://localhost:8080/examples/servlets/servlet/HelloWorldExample | grep 'Hello World!'"
+    )
+    machine.succeed(
+        "curl -sS --fail http://localhost:8080/examples/jsp/jsp2/simpletag/hello.jsp | grep 'Hello, world!'"
+    )
+    machine.succeed(
+        "curl -sS --fail http://localhost:8080/axis2/axis2-web/HappyAxis.jsp | grep 'Found Axis2'"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tor.nix b/nixpkgs/nixos/tests/tor.nix
new file mode 100644
index 000000000000..b55fbf91232c
--- /dev/null
+++ b/nixpkgs/nixos/tests/tor.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "tor";
+  meta.maintainers = with lib.maintainers; [ joachifm ];
+
+  nodes.client = { pkgs, ... }: {
+    boot.kernelParams = [ "audit=0" "apparmor=0" "quiet" ];
+    networking.firewall.enable = false;
+    networking.useDHCP = false;
+
+    environment.systemPackages = [ pkgs.netcat ];
+    services.tor.enable = true;
+    services.tor.client.enable = true;
+    services.tor.settings.ControlPort = 9051;
+  };
+
+  testScript = ''
+    client.wait_for_unit("tor.service")
+    client.wait_for_open_port(9051)
+    assert "514 Authentication required." in client.succeed(
+        "echo GETINFO version | nc 127.0.0.1 9051"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tracee.nix b/nixpkgs/nixos/tests/tracee.nix
new file mode 100644
index 000000000000..3dadc0f9fdb3
--- /dev/null
+++ b/nixpkgs/nixos/tests/tracee.nix
@@ -0,0 +1,68 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "tracee-integration";
+  meta.maintainers = pkgs.tracee.meta.maintainers;
+
+  nodes = {
+    machine = { config, pkgs, ... }: {
+      # EventFilters/trace_only_events_from_new_containers and
+      # Test_EventFilters/trace_only_events_from_"dockerd"_binary_and_contain_it's_pid
+      # require docker/dockerd
+      virtualisation.docker.enable = true;
+
+      environment.systemPackages = with pkgs; [
+        # required by Test_EventFilters/trace_events_from_ls_and_which_binary_in_separate_scopes
+        which
+        # build the go integration tests as a binary
+        (tracee.overrideAttrs (oa: {
+          pname = oa.pname + "-integration";
+          postPatch = oa.postPatch or "" + ''
+            # prepare tester.sh (which will be embedded in the test binary)
+            patchShebangs tests/integration/tester.sh
+
+            # fix the test to look at nixos paths for running programs
+            substituteInPlace tests/integration/integration_test.go \
+              --replace "bin=/usr/bin/" "comm=" \
+              --replace "binary=/usr/bin/" "comm=" \
+              --replace "/usr/bin/dockerd" "dockerd" \
+              --replace "/usr/bin" "/run/current-system/sw/bin"
+          '';
+          nativeBuildInputs = oa.nativeBuildInputs or [ ] ++ [ makeWrapper ];
+          buildPhase = ''
+            runHook preBuild
+            # just build the static lib we need for the go test binary
+            make $makeFlags ''${enableParallelBuilding:+-j$NIX_BUILD_CORES} bpf-core ./dist/btfhub
+
+            # then compile the tests to be ran later
+            CGO_LDFLAGS="$(pkg-config --libs libbpf)" go test -tags core,ebpf,integration -p 1 -c -o $GOPATH/tracee-integration ./tests/integration/...
+            runHook postBuild
+          '';
+          doCheck = false;
+          outputs = [ "out" ];
+          installPhase = ''
+            mkdir -p $out/bin
+            mv $GOPATH/tracee-integration $out/bin/
+          '';
+          doInstallCheck = false;
+
+          meta = oa.meta // {
+            outputsToInstall = [];
+          };
+        }))
+      ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("docker.service")
+
+    with subtest("run integration tests"):
+      # EventFilters/trace_only_events_from_new_containers also requires a container called "alpine"
+      machine.succeed('tar c -C ${pkgs.pkgsStatic.busybox} . | docker import - alpine --change "ENTRYPOINT [\"sleep\"]"')
+
+      # Test_EventFilters/trace_event_set_in_a_specific_scope expects to be in a dir that includes "integration"
+      print(machine.succeed(
+        'mkdir /tmp/integration',
+        'cd /tmp/integration && tracee-integration -test.v'
+      ))
+  '';
+})
diff --git a/nixpkgs/nixos/tests/traefik.nix b/nixpkgs/nixos/tests/traefik.nix
new file mode 100644
index 000000000000..ce808e6ec95a
--- /dev/null
+++ b/nixpkgs/nixos/tests/traefik.nix
@@ -0,0 +1,98 @@
+# Test Traefik as a reverse proxy of a local web service
+# and a Docker container.
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "traefik";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ joko ];
+  };
+
+  nodes = {
+    client = { config, pkgs, ... }: {
+      environment.systemPackages = [ pkgs.curl ];
+    };
+    traefik = { config, pkgs, ... }: {
+      virtualisation.oci-containers = {
+        backend = "docker";
+        containers.nginx = {
+          extraOptions = [
+            "-l"
+            "traefik.enable=true"
+            "-l"
+            "traefik.http.routers.nginx.entrypoints=web"
+            "-l"
+            "traefik.http.routers.nginx.rule=Host(`nginx.traefik.test`)"
+          ];
+          image = "nginx-container";
+          imageFile = pkgs.dockerTools.examples.nginx;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.traefik = {
+        enable = true;
+
+        dynamicConfigOptions = {
+          http.routers.simplehttp = {
+            rule = "Host(`simplehttp.traefik.test`)";
+            entryPoints = [ "web" ];
+            service = "simplehttp";
+          };
+
+          http.services.simplehttp = {
+            loadBalancer.servers = [{
+              url = "http://127.0.0.1:8000";
+            }];
+          };
+        };
+
+        staticConfigOptions = {
+          global = {
+            checkNewVersion = false;
+            sendAnonymousUsage = false;
+          };
+
+          entryPoints.web.address = ":\${HTTP_PORT}";
+
+          providers.docker.exposedByDefault = false;
+        };
+        environmentFiles = [(pkgs.writeText "traefik.env" ''
+          HTTP_PORT=80
+        '')];
+      };
+
+      systemd.services.simplehttp = {
+        script = "${pkgs.python3}/bin/python -m http.server 8000";
+        serviceConfig.Type = "simple";
+        wantedBy = [ "multi-user.target" ];
+      };
+
+      users.users.traefik.extraGroups = [ "docker" ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    traefik.wait_for_unit("docker-nginx.service")
+    traefik.wait_until_succeeds("docker ps | grep nginx-container")
+    traefik.wait_for_unit("simplehttp.service")
+    traefik.wait_for_unit("traefik.service")
+    traefik.wait_for_open_port(80)
+    traefik.wait_for_unit("multi-user.target")
+
+    client.wait_for_unit("multi-user.target")
+
+    client.wait_until_succeeds("curl -sSf -H Host:nginx.traefik.test http://traefik/")
+
+    with subtest("Check that a container can be reached via Traefik"):
+        assert "Hello from NGINX" in client.succeed(
+            "curl -sSf -H Host:nginx.traefik.test http://traefik/"
+        )
+
+    with subtest("Check that dynamic configuration works"):
+        assert "Directory listing for " in client.succeed(
+            "curl -sSf -H Host:simplehttp.traefik.test http://traefik/"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/trafficserver.nix b/nixpkgs/nixos/tests/trafficserver.nix
new file mode 100644
index 000000000000..94d0e4dd926e
--- /dev/null
+++ b/nixpkgs/nixos/tests/trafficserver.nix
@@ -0,0 +1,179 @@
+# verifies:
+#   1. Traffic Server is able to start
+#   2. Traffic Server spawns traffic_crashlog upon startup
+#   3. Traffic Server proxies HTTP requests according to URL remapping rules
+#      in 'services.trafficserver.remap'
+#   4. Traffic Server applies per-map settings specified with the conf_remap
+#      plugin
+#   5. Traffic Server caches HTTP responses
+#   6. Traffic Server processes HTTP PUSH requests
+#   7. Traffic Server can load the healthchecks plugin
+#   8. Traffic Server logs HTTP traffic as configured
+#
+# uses:
+#   - bin/traffic_manager
+#   - bin/traffic_server
+#   - bin/traffic_crashlog
+#   - bin/traffic_cache_tool
+#   - bin/traffic_ctl
+#   - bin/traffic_logcat
+#   - bin/traffic_logstats
+#   - bin/tspush
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "trafficserver";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ midchildan ];
+  };
+
+  nodes = {
+    ats = { pkgs, lib, config, ... }: let
+      user = config.users.users.trafficserver.name;
+      group = config.users.groups.trafficserver.name;
+      healthchecks = pkgs.writeText "healthchecks.conf" ''
+        /status /tmp/ats.status text/plain 200 500
+      '';
+    in {
+      services.trafficserver.enable = true;
+
+      services.trafficserver.records = {
+        proxy.config.http.server_ports = "80 80:ipv6";
+        proxy.config.hostdb.host_file.path = "/etc/hosts";
+        proxy.config.log.max_space_mb_headroom = 0;
+        proxy.config.http.push_method_enabled = 1;
+
+        # check that cache storage is usable before accepting traffic
+        proxy.config.http.wait_for_cache = 2;
+      };
+
+      services.trafficserver.plugins = [
+        { path = "healthchecks.so"; arg = toString healthchecks; }
+        { path = "xdebug.so"; }
+      ];
+
+      services.trafficserver.remap = ''
+        map http://httpbin.test http://httpbin
+        map http://pristine-host-hdr.test http://httpbin \
+          @plugin=conf_remap.so \
+          @pparam=proxy.config.url_remap.pristine_host_hdr=1
+        map http://ats/tspush http://httpbin/cache \
+          @plugin=conf_remap.so \
+          @pparam=proxy.config.http.cache.required_headers=0
+      '';
+
+      services.trafficserver.storage = ''
+        /dev/vdb volume=1
+      '';
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      virtualisation.emptyDiskImages = [ 256 ];
+      services.udev.extraRules = ''
+        KERNEL=="vdb", OWNER="${user}", GROUP="${group}"
+      '';
+    };
+
+    httpbin = { pkgs, lib, ... }: let
+      python = pkgs.python3.withPackages
+        (ps: with ps; [ httpbin gunicorn gevent ]);
+    in {
+      systemd.services.httpbin = {
+        enable = true;
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${python}/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent";
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+    };
+
+    client = { pkgs, lib, ... }: {
+      environment.systemPackages = with pkgs; [ curl ];
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    sampleFile = pkgs.writeText "sample.txt" ''
+      It's the season of White Album.
+    '';
+  in ''
+    import json
+    import re
+
+    ats.wait_for_unit("trafficserver")
+    ats.wait_for_open_port(80)
+    httpbin.wait_for_unit("httpbin")
+    httpbin.wait_for_open_port(80)
+    client.systemctl("start network-online.target")
+    client.wait_for_unit("network-online.target")
+
+    with subtest("Traffic Server is running"):
+        out = ats.succeed("traffic_ctl server status")
+        assert out.strip() == "Proxy -- on"
+
+    with subtest("traffic_crashlog is running"):
+        ats.succeed("pgrep -f traffic_crashlog")
+
+    with subtest("basic remapping works"):
+        out = client.succeed("curl -vv -H 'Host: httpbin.test' http://ats/headers")
+        assert json.loads(out)["headers"]["Host"] == "httpbin"
+
+    with subtest("conf_remap plugin works"):
+        out = client.succeed(
+            "curl -vv -H 'Host: pristine-host-hdr.test' http://ats/headers"
+        )
+        assert json.loads(out)["headers"]["Host"] == "pristine-host-hdr.test"
+
+    with subtest("caching works"):
+        out = client.succeed(
+            "curl -vv -D - -H 'Host: httpbin.test' -H 'X-Debug: X-Cache' http://ats/cache/60 -o /dev/null"
+        )
+        assert "X-Cache: miss" in out
+
+        out = client.succeed(
+            "curl -vv -D - -H 'Host: httpbin.test' -H 'X-Debug: X-Cache' http://ats/cache/60 -o /dev/null"
+        )
+        assert "X-Cache: hit-fresh" in out
+
+    with subtest("pushing to cache works"):
+        url = "http://ats/tspush"
+
+        ats.succeed(f"echo {url} > /tmp/urls.txt")
+        out = ats.succeed(
+            f"tspush -f '${sampleFile}' -u {url}"
+        )
+        assert "HTTP/1.0 201 Created" in out, "cache push failed"
+
+        out = ats.succeed(
+            "traffic_cache_tool --spans /etc/trafficserver/storage.config find --input /tmp/urls.txt"
+        )
+        assert "Span: /dev/vdb" in out, "cache not stored on disk"
+
+        out = client.succeed(f"curl {url}").strip()
+        expected = (
+            open("${sampleFile}").read().strip()
+        )
+        assert out == expected, "cache content mismatch"
+
+    with subtest("healthcheck plugin works"):
+        out = client.succeed("curl -vv http://ats/status -o /dev/null -w '%{http_code}'")
+        assert out.strip() == "500"
+
+        ats.succeed("touch /tmp/ats.status")
+
+        out = client.succeed("curl -vv http://ats/status -o /dev/null -w '%{http_code}'")
+        assert out.strip() == "200"
+
+    with subtest("logging works"):
+        access_log_path = "/var/log/trafficserver/squid.blog"
+        ats.wait_for_file(access_log_path)
+
+        out = ats.succeed(f"traffic_logcat {access_log_path}").split("\n")[0]
+        expected = "^\S+ \S+ \S+ TCP_MISS/200 \S+ GET http://httpbin/headers - DIRECT/httpbin application/json$"
+        assert re.fullmatch(expected, out) is not None, "no matching logs"
+
+        out = json.loads(ats.succeed(f"traffic_logstats -jf {access_log_path}"))
+        assert isinstance(out, dict)
+        assert out["total"]["error.total"]["req"] == "0", "unexpected log stat"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/transmission.nix b/nixpkgs/nixos/tests/transmission.nix
new file mode 100644
index 000000000000..03fc9a421510
--- /dev/null
+++ b/nixpkgs/nixos/tests/transmission.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, transmission, ... }: {
+  name = "transmission";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ coconnor ];
+  };
+
+  nodes.machine = { ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+
+    networking.firewall.allowedTCPPorts = [ 9091 ];
+
+    security.apparmor.enable = true;
+
+    services.transmission.enable = true;
+    services.transmission.package = transmission;
+  };
+
+  testScript =
+    ''
+      start_all()
+      machine.wait_for_unit("transmission")
+      machine.shutdown()
+    '';
+})
diff --git a/nixpkgs/nixos/tests/trezord.nix b/nixpkgs/nixos/tests/trezord.nix
new file mode 100644
index 000000000000..fb60cb4aff10
--- /dev/null
+++ b/nixpkgs/nixos/tests/trezord.nix
@@ -0,0 +1,19 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "trezord";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [ mmahut _1000101 ];
+  };
+  nodes = {
+    machine = { ... }: {
+      services.trezord.enable = true;
+      services.trezord.emulator.enable = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("trezord.service")
+    machine.wait_for_open_port(21325)
+    machine.wait_until_succeeds("curl -fL http://localhost:21325/status/ | grep Version")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/trickster.nix b/nixpkgs/nixos/tests/trickster.nix
new file mode 100644
index 000000000000..acb2e735c39f
--- /dev/null
+++ b/nixpkgs/nixos/tests/trickster.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "trickster";
+  meta = with pkgs.lib; {
+    maintainers = with maintainers; [ _1000101 ];
+  };
+
+  nodes = {
+    prometheus = { ... }: {
+      services.prometheus.enable = true;
+      networking.firewall.allowedTCPPorts = [ 9090 ];
+    };
+    trickster = { ... }: {
+      services.trickster.enable = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+    prometheus.wait_for_unit("prometheus.service")
+    prometheus.wait_for_open_port(9090)
+    prometheus.wait_until_succeeds(
+        "curl -fL http://localhost:9090/metrics | grep 'promhttp_metric_handler_requests_total{code=\"500\"} 0'"
+    )
+    trickster.wait_for_unit("trickster.service")
+    trickster.wait_for_open_port(8082)
+    trickster.wait_for_open_port(9090)
+    trickster.wait_until_succeeds(
+        "curl -fL http://localhost:8082/metrics | grep 'promhttp_metric_handler_requests_total{code=\"500\"} 0'"
+    )
+    trickster.wait_until_succeeds(
+        "curl -fL http://prometheus:9090/metrics | grep 'promhttp_metric_handler_requests_total{code=\"500\"} 0'"
+    )
+    trickster.wait_until_succeeds(
+        "curl -fL http://localhost:9090/metrics | grep 'promhttp_metric_handler_requests_total{code=\"500\"} 0'"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/trilium-server.nix b/nixpkgs/nixos/tests/trilium-server.nix
new file mode 100644
index 000000000000..6346575b33da
--- /dev/null
+++ b/nixpkgs/nixos/tests/trilium-server.nix
@@ -0,0 +1,53 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "trilium-server";
+  nodes = {
+    default = {
+      services.trilium-server.enable = true;
+    };
+    configured = {
+      services.trilium-server = {
+        enable = true;
+        dataDir = "/data/trilium";
+      };
+    };
+
+    nginx = {
+      services.trilium-server = {
+        enable = true;
+        nginx.enable = true;
+        nginx.hostName = "trilium.example.com";
+      };
+    };
+  };
+
+  testScript =
+    ''
+      start_all()
+
+      with subtest("by default works without configuration"):
+          default.wait_for_unit("trilium-server.service")
+
+      with subtest("by default available on port 8080"):
+          default.wait_for_unit("trilium-server.service")
+          default.wait_for_open_port(8080)
+          # we output to /dev/null here to avoid a python UTF-8 decode error
+          # but the check will still fail if the service doesn't respond
+          default.succeed("curl --fail -o /dev/null 127.0.0.1:8080")
+
+      with subtest("by default creates empty document"):
+          default.wait_for_unit("trilium-server.service")
+          default.succeed("test -f /var/lib/trilium/document.db")
+
+      with subtest("configured with custom data store"):
+          configured.wait_for_unit("trilium-server.service")
+          configured.succeed("test -f /data/trilium/document.db")
+
+      with subtest("nginx with custom host name"):
+          nginx.wait_for_unit("trilium-server.service")
+          nginx.wait_for_unit("nginx.service")
+
+          nginx.succeed(
+              "curl --resolve 'trilium.example.com:80:127.0.0.1' http://trilium.example.com/"
+          )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/tsja.nix b/nixpkgs/nixos/tests/tsja.nix
new file mode 100644
index 000000000000..f34358ff3f5f
--- /dev/null
+++ b/nixpkgs/nixos/tests/tsja.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "tsja";
+  meta = {
+    maintainers = with lib.maintainers; [ chayleaf ];
+  };
+
+  nodes = {
+    master =
+      { config, ... }:
+
+      {
+        services.postgresql = {
+          enable = true;
+          extraPlugins = ps: with ps; [
+            tsja
+          ];
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    master.wait_for_unit("postgresql")
+    master.succeed("sudo -u postgres psql -f /run/current-system/sw/share/postgresql/extension/libtsja_dbinit.sql")
+    # make sure "日本語" is parsed as a separate lexeme
+    master.succeed("""
+      sudo -u postgres \\
+        psql -c "SELECT * FROM ts_debug('japanese', 'PostgreSQLã§æ—¥æœ¬èªžã®ãƒ†ã‚­ã‚¹ãƒˆæ¤œç´¢ãŒã§ãã¾ã™ã€‚')" \\
+          | grep "{日本語}"
+    """)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/tsm-client-gui.nix b/nixpkgs/nixos/tests/tsm-client-gui.nix
new file mode 100644
index 000000000000..ca10cddc411f
--- /dev/null
+++ b/nixpkgs/nixos/tests/tsm-client-gui.nix
@@ -0,0 +1,57 @@
+# The tsm-client GUI first tries to connect to a server.
+# We can't simulate a server, so we just check if
+# it reports the correct connection failure error.
+# After that the test persuades the GUI
+# to show its main application window
+# and verifies some configuration information.
+
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "tsm-client";
+
+  enableOCR = true;
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/x11.nix ];
+    programs.tsmClient = {
+      enable = true;
+      package = pkgs.tsm-client-withGui;
+      defaultServername = "testserver";
+      servers.testserver = {
+        # 192.0.0.8 is a "dummy address" according to RFC 7600
+        tcpserveraddress = "192.0.0.8";
+        nodename = "SOME-NODE";
+        passworddir = "/tmp";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.succeed("which dsmj")  # fail early if this is missing
+    machine.wait_for_x()
+    machine.execute("DSM_LOG=/tmp dsmj -optfile=/dev/null >&2 &")
+
+    # does it report the "TCP/IP connection failure" error code?
+    machine.wait_for_window("IBM Storage Protect")
+    machine.wait_for_text("ANS2610S")
+    machine.send_key("esc")
+
+    # it asks to continue to restore a local backupset now;
+    # "yes" (return) leads to the main application window
+    machine.wait_for_text("backupset")
+    machine.send_key("ret")
+
+    # main window: navigate to "Connection Information"
+    machine.wait_for_text("Welcome")
+    machine.send_key("alt-f")  # "File" menu
+    machine.send_key("c")  # "Connection Information"
+
+    # "Connection Information" dialog box
+    machine.wait_for_window("Connection Information")
+    machine.wait_for_text("SOME-NODE")
+    machine.wait_for_text("${pkgs.tsm-client.passthru.unwrapped.version}")
+
+    machine.shutdown()
+  '';
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+})
diff --git a/nixpkgs/nixos/tests/tuptime.nix b/nixpkgs/nixos/tests/tuptime.nix
new file mode 100644
index 000000000000..93410de7bdf5
--- /dev/null
+++ b/nixpkgs/nixos/tests/tuptime.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "tuptime";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ evils ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ../modules/profiles/minimal.nix ];
+    services.tuptime.enable = true;
+  };
+
+  testScript =
+    ''
+      # see if it starts
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("tuptime | grep 'System startups:[[:blank:]]*1'")
+      machine.succeed("tuptime | grep 'System uptime:[[:blank:]]*100.0%'")
+      machine.shutdown()
+
+      # restart machine and see if it correctly reports the reboot
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("tuptime | grep 'System startups:[[:blank:]]*2'")
+      machine.succeed("tuptime | grep 'System shutdowns:[[:blank:]]*1 ok'")
+      machine.shutdown()
+    '';
+})
+
diff --git a/nixpkgs/nixos/tests/turbovnc-headless-server.nix b/nixpkgs/nixos/tests/turbovnc-headless-server.nix
new file mode 100644
index 000000000000..a155f9f907b2
--- /dev/null
+++ b/nixpkgs/nixos/tests/turbovnc-headless-server.nix
@@ -0,0 +1,172 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "turbovnc-headless-server";
+  meta = {
+    maintainers = with lib.maintainers; [ nh2 ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+
+    environment.systemPackages = with pkgs; [
+      glxinfo
+      procps # for `pkill`, `pidof` in the test
+      scrot # for screenshotting Xorg
+      turbovnc
+    ];
+
+    programs.turbovnc.ensureHeadlessSoftwareOpenGL = true;
+
+    networking.firewall = {
+      # Reject instead of drop, for failures instead of hangs.
+      rejectPackets = true;
+      allowedTCPPorts = [
+        5900 # VNC :0, for seeing what's going on in the server
+      ];
+    };
+
+    # So that we can ssh into the VM, see e.g.
+    # http://blog.patapon.info/nixos-local-vm/#accessing-the-vm-with-ssh
+    services.openssh.enable = true;
+    services.openssh.settings.PermitRootLogin = "yes";
+    users.extraUsers.root.password = "";
+    users.mutableUsers = false;
+  };
+
+  testScript = ''
+    def wait_until_terminated_or_succeeds(
+        termination_check_shell_command,
+        success_check_shell_command,
+        get_detail_message_fn,
+        retries=60,
+        retry_sleep=0.5,
+    ):
+        def check_success():
+            command_exit_code, _output = machine.execute(success_check_shell_command)
+            return command_exit_code == 0
+
+        for _ in range(retries):
+            exit_check_exit_code, _output = machine.execute(termination_check_shell_command)
+            is_terminated = exit_check_exit_code != 0
+            if is_terminated:
+                if check_success():
+                    return
+                else:
+                    details = get_detail_message_fn()
+                    raise Exception(
+                        f"termination check ({termination_check_shell_command}) triggered without command succeeding ({success_check_shell_command}); details: {details}"
+                    )
+            else:
+                if check_success():
+                    return
+            import time
+            time.sleep(retry_sleep)
+
+        if not check_success():
+            details = get_detail_message_fn()
+            raise Exception(
+                f"action timed out ({success_check_shell_command}); details: {details}"
+            )
+
+
+    # Below we use the pattern:
+    #     (cmd | tee stdout.log) 3>&1 1>&2 2>&3 | tee stderr.log
+    # to capture both stderr and stdout while also teeing them, see:
+    # https://unix.stackexchange.com/questions/6430/how-to-redirect-stderr-and-stdout-to-different-files-and-also-display-in-termina/6431#6431
+
+
+    # Starts headless VNC server, backgrounding it.
+    def start_xvnc():
+        xvnc_command = " ".join(
+            [
+                "Xvnc",
+                ":0",
+                "-iglx",
+                "-auth /root/.Xauthority",
+                "-geometry 1240x900",
+                "-depth 24",
+                "-rfbwait 5000",
+                "-deferupdate 1",
+                "-verbose",
+                "-securitytypes none",
+                # We don't enforce localhost listening such that we
+                # can connect from outside the VM using
+                #     env QEMU_NET_OPTS=hostfwd=tcp::5900-:5900 $(nix-build nixos/tests/turbovnc-headless-server.nix -A driver)/bin/nixos-test-driver
+                # for testing purposes, and so that we can in the future
+                # add another test case that connects the TurboVNC client.
+                # "-localhost",
+            ]
+        )
+        machine.execute(
+            # Note trailing & for backgrounding.
+            f"({xvnc_command} | tee /tmp/Xvnc.stdout) 3>&1 1>&2 2>&3 | tee /tmp/Xvnc.stderr >&2 &",
+        )
+
+
+    # Waits until the server log message that tells us that GLX is ready
+    # (requires `-verbose` above), avoiding screenshoting racing below.
+    def wait_until_xvnc_glx_ready():
+        machine.wait_until_succeeds("test -f /tmp/Xvnc.stderr")
+        wait_until_terminated_or_succeeds(
+            termination_check_shell_command="pidof Xvnc",
+            success_check_shell_command="grep 'GLX: Initialized DRISWRAST' /tmp/Xvnc.stderr",
+            get_detail_message_fn=lambda: "Contents of /tmp/Xvnc.stderr:\n"
+            + machine.succeed("cat /tmp/Xvnc.stderr"),
+        )
+
+
+    # Checks that we detect glxgears failing when
+    # `LIBGL_DRIVERS_PATH=/nonexistent` is set
+    # (in which case software rendering should not work).
+    def test_glxgears_failing_with_bad_driver_path():
+        machine.execute(
+            # Note trailing & for backgrounding.
+            "(env DISPLAY=:0 LIBGL_DRIVERS_PATH=/nonexistent glxgears -info | tee /tmp/glxgears-should-fail.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears-should-fail.stderr >&2 &"
+        )
+        machine.wait_until_succeeds("test -f /tmp/glxgears-should-fail.stderr")
+        wait_until_terminated_or_succeeds(
+            termination_check_shell_command="pidof glxgears",
+            success_check_shell_command="grep 'libGL error: failed to load driver: swrast' /tmp/glxgears-should-fail.stderr",
+            get_detail_message_fn=lambda: "Contents of /tmp/glxgears-should-fail.stderr:\n"
+            + machine.succeed("cat /tmp/glxgears-should-fail.stderr"),
+        )
+        machine.wait_until_fails("pidof glxgears")
+
+
+    # Starts glxgears, backgrounding it. Waits until it prints the `GL_RENDERER`.
+    # Does not quit glxgears.
+    def test_glxgears_prints_renderer():
+        machine.execute(
+            # Note trailing & for backgrounding.
+            "(env DISPLAY=:0 glxgears -info | tee /tmp/glxgears.stdout) 3>&1 1>&2 2>&3 | tee /tmp/glxgears.stderr >&2 &"
+        )
+        machine.wait_until_succeeds("test -f /tmp/glxgears.stderr")
+        wait_until_terminated_or_succeeds(
+            termination_check_shell_command="pidof glxgears",
+            success_check_shell_command="grep 'GL_RENDERER' /tmp/glxgears.stdout",
+            get_detail_message_fn=lambda: "Contents of /tmp/glxgears.stderr:\n"
+            + machine.succeed("cat /tmp/glxgears.stderr"),
+        )
+
+
+    with subtest("Start Xvnc"):
+        start_xvnc()
+        wait_until_xvnc_glx_ready()
+
+    with subtest("Ensure bad driver path makes glxgears fail"):
+        test_glxgears_failing_with_bad_driver_path()
+
+    with subtest("Run 3D application (glxgears)"):
+        test_glxgears_prints_renderer()
+
+        # Take screenshot; should display the glxgears.
+        machine.succeed("scrot --display :0 /tmp/glxgears.png")
+
+    # Copy files down.
+    machine.copy_from_vm("/tmp/glxgears.png")
+    machine.copy_from_vm("/tmp/glxgears.stdout")
+    machine.copy_from_vm("/tmp/glxgears-should-fail.stdout")
+    machine.copy_from_vm("/tmp/glxgears-should-fail.stderr")
+    machine.copy_from_vm("/tmp/Xvnc.stdout")
+    machine.copy_from_vm("/tmp/Xvnc.stderr")
+  '';
+
+})
diff --git a/nixpkgs/nixos/tests/tuxguitar.nix b/nixpkgs/nixos/tests/tuxguitar.nix
new file mode 100644
index 000000000000..00833024bfea
--- /dev/null
+++ b/nixpkgs/nixos/tests/tuxguitar.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "tuxguitar";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+
+    environment.systemPackages = [ pkgs.tuxguitar ];
+  };
+
+  testScript = ''
+    machine.wait_for_x()
+    machine.succeed("tuxguitar >&2 &")
+    machine.wait_for_window("TuxGuitar - Untitled.tg")
+    machine.sleep(1)
+    machine.screenshot("tuxguitar")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/twingate.nix b/nixpkgs/nixos/tests/twingate.nix
new file mode 100644
index 000000000000..f8bede09d9f2
--- /dev/null
+++ b/nixpkgs/nixos/tests/twingate.nix
@@ -0,0 +1,14 @@
+{
+  name = "twingate";
+
+  nodes.machine.services.twingate.enable = true;
+
+  testScript = { nodes, ... }: ''
+    machine.wait_for_unit("twingate.service")
+    machine.succeed("twingate --version | grep '${nodes.machine.services.twingate.package.version}' >&2")
+    machine.succeed("twingate config log-level 'debug'")
+    machine.systemctl("restart twingate.service")
+    machine.succeed("grep 'debug' /etc/twingate/log_level.conf >&2")
+    machine.succeed("twingate config log-level | grep 'debug' >&2")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/txredisapi.nix b/nixpkgs/nixos/tests/txredisapi.nix
new file mode 100644
index 000000000000..47c2ba6d3749
--- /dev/null
+++ b/nixpkgs/nixos/tests/txredisapi.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "txredisapi";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ dandellion ];
+  };
+
+  nodes = {
+    machine =
+      { pkgs, ... }:
+
+      {
+        services.redis.servers."".enable = true;
+
+        environment.systemPackages = with pkgs; [ (python3.withPackages (ps: [ ps.twisted ps.txredisapi ps.mock ]))];
+      };
+  };
+
+  testScript = { nodes, ... }: let
+    inherit (nodes.machine.config.services) redis;
+    in ''
+    start_all()
+    machine.wait_for_unit("redis")
+    machine.wait_for_file("${redis.servers."".unixSocket}")
+    machine.succeed("ln -s ${redis.servers."".unixSocket} /tmp/redis.sock")
+
+    tests = machine.succeed("PYTHONPATH=\"${pkgs.python3Packages.txredisapi.src}\" python -m twisted.trial ${pkgs.python3Packages.txredisapi.src}/tests")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/typesense.nix b/nixpkgs/nixos/tests/typesense.nix
new file mode 100644
index 000000000000..87ed248257ea
--- /dev/null
+++ b/nixpkgs/nixos/tests/typesense.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: let
+  testPort = 8108;
+in {
+  name = "typesense";
+  meta.maintainers = with pkgs.lib.maintainers; [ oddlama ];
+
+  nodes.machine = { ... }: {
+    services.typesense = {
+      enable = true;
+      apiKeyFile = pkgs.writeText "typesense-api-key" "dummy";
+      settings.server = {
+        api-port = testPort;
+        api-address = "0.0.0.0";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("typesense.service")
+    machine.wait_for_open_port(${toString testPort})
+    # After waiting for the port, typesense still hasn't initialized the database, so wait until we can connect successfully
+    assert machine.wait_until_succeeds("curl --fail http://localhost:${toString testPort}/health") == '{"ok":true}'
+  '';
+})
diff --git a/nixpkgs/nixos/tests/ucarp.nix b/nixpkgs/nixos/tests/ucarp.nix
new file mode 100644
index 000000000000..1f60f770d3a8
--- /dev/null
+++ b/nixpkgs/nixos/tests/ucarp.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+let
+  addrShared = "192.168.0.1";
+  addrHostA = "192.168.0.10";
+  addrHostB = "192.168.0.11";
+
+  mkUcarpHost = addr: { config, pkgs, lib, ... }: {
+    networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
+      { address = addr; prefixLength = 24; }
+    ];
+
+    networking.ucarp = {
+      enable = true;
+      interface = "eth1";
+      srcIp = addr;
+      vhId = 1;
+      passwordFile = "${pkgs.writeText "ucarp-pass" "secure"}";
+      addr = addrShared;
+      upscript = pkgs.writeScript "upscript" ''
+        #!/bin/sh
+        ${pkgs.iproute2}/bin/ip addr add "$2"/24 dev "$1"
+      '';
+      downscript = pkgs.writeScript "downscript" ''
+        #!/bin/sh
+        ${pkgs.iproute2}/bin/ip addr del "$2"/24 dev "$1"
+      '';
+    };
+  };
+in {
+  name = "ucarp";
+  meta.maintainers = with lib.maintainers; [ oxzi ];
+
+  nodes = {
+    hostA = mkUcarpHost addrHostA;
+    hostB = mkUcarpHost addrHostB;
+  };
+
+  testScript = ''
+    def is_master(host):
+      ipOutput = host.succeed("ip addr show dev eth1")
+      return "inet ${addrShared}/24" in ipOutput
+
+
+    start_all()
+
+    # First, let both hosts start and let a master node be selected
+    for host, peer in [(hostA, "${addrHostB}"), (hostB, "${addrHostA}")]:
+      host.wait_for_unit("ucarp.service")
+      host.succeed(f"ping -c 1 {peer}")
+
+    hostA.sleep(5)
+
+    hostA_master, hostB_master = is_master(hostA), is_master(hostB)
+    assert hostA_master != hostB_master, "only one master node is allowed"
+
+    master_host = hostA if hostA_master else hostB
+    backup_host = hostB if hostA_master else hostA
+
+    # Let's crash the master host and let the backup take over
+    master_host.crash()
+
+    backup_host.sleep(5)
+    assert is_master(backup_host), "backup did not take over"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/udisks2.nix b/nixpkgs/nixos/tests/udisks2.nix
new file mode 100644
index 000000000000..8cc148750c7b
--- /dev/null
+++ b/nixpkgs/nixos/tests/udisks2.nix
@@ -0,0 +1,72 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+
+  stick = pkgs.fetchurl {
+    url = "https://nixos.org/~eelco/nix/udisks-test.img.xz";
+    sha256 = "0was1xgjkjad91nipzclaz5biv3m4b2nk029ga6nk7iklwi19l8b";
+  };
+
+in
+
+{
+  name = "udisks2";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ eelco ];
+  };
+
+  nodes.machine =
+    { ... }:
+    { services.udisks2.enable = true;
+      imports = [ ./common/user-account.nix ];
+
+      security.polkit.extraConfig =
+        ''
+          polkit.addRule(function(action, subject) {
+            if (subject.user == "alice") return "yes";
+          });
+        '';
+    };
+
+  testScript =
+    ''
+      import lzma
+
+      machine.systemctl("start udisks2")
+      machine.wait_for_unit("udisks2.service")
+
+      with lzma.open(
+          "${stick}"
+      ) as data, open(machine.state_dir / "usbstick.img", "wb") as stick:
+          stick.write(data.read())
+
+      machine.succeed("udisksctl info -b /dev/vda >&2")
+      machine.fail("udisksctl info -b /dev/sda1")
+
+      # Attach a USB stick and wait for it to show up.
+      machine.send_monitor_command(
+          f"drive_add 0 id=stick,if=none,file={stick.name},format=raw"
+      )
+      machine.send_monitor_command("device_add usb-storage,id=stick,drive=stick")
+      machine.wait_until_succeeds("udisksctl info -b /dev/sda1")
+      machine.succeed("udisksctl info -b /dev/sda1 | grep 'IdLabel:.*USBSTICK'")
+
+      # Mount the stick as a non-root user and do some stuff with it.
+      machine.succeed("su - alice -c 'udisksctl info -b /dev/sda1'")
+      machine.succeed("su - alice -c 'udisksctl mount -b /dev/sda1'")
+      machine.succeed(
+          "su - alice -c 'cat /run/media/alice/USBSTICK/test.txt' | grep -q 'Hello World'"
+      )
+      machine.succeed("su - alice -c 'echo foo > /run/media/alice/USBSTICK/bar.txt'")
+
+      # Unmounting the stick should make the mountpoint disappear.
+      machine.succeed("su - alice -c 'udisksctl unmount -b /dev/sda1'")
+      machine.fail("[ -d /run/media/alice/USBSTICK ]")
+
+      # Remove the USB stick.
+      machine.send_monitor_command("device_del stick")
+      machine.wait_until_fails("udisksctl info -b /dev/sda1")
+      machine.fail("[ -e /dev/sda ]")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/ulogd/ulogd.nix b/nixpkgs/nixos/tests/ulogd/ulogd.nix
new file mode 100644
index 000000000000..0fa92229a100
--- /dev/null
+++ b/nixpkgs/nixos/tests/ulogd/ulogd.nix
@@ -0,0 +1,56 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "ulogd";
+
+  meta.maintainers = with lib.maintainers; [ p-h ];
+
+  nodes.machine = { ... }: {
+    networking.firewall.enable = false;
+    networking.nftables.enable = true;
+    networking.nftables.ruleset = ''
+      table inet filter {
+        chain input {
+          type filter hook input priority 0;
+          icmp type { echo-request, echo-reply } log group 2 accept
+        }
+
+        chain output {
+          type filter hook output priority 0; policy accept;
+          icmp type { echo-request, echo-reply } log group 2 accept
+        }
+
+        chain forward {
+          type filter hook forward priority 0; policy drop;
+        }
+
+      }
+    '';
+    services.ulogd = {
+      enable = true;
+      settings = {
+        global = {
+          logfile = "/var/log/ulogd.log";
+          stack = [
+            "log1:NFLOG,base1:BASE,ifi1:IFINDEX,ip2str1:IP2STR,print1:PRINTPKT,emu1:LOGEMU"
+            "log1:NFLOG,base1:BASE,pcap1:PCAP"
+          ];
+        };
+
+        log1.group = 2;
+
+        pcap1 = {
+          sync = 1;
+          file = "/var/log/ulogd.pcap";
+        };
+
+        emu1 = {
+          sync = 1;
+          file = "/var/log/ulogd_pkts.log";
+        };
+      };
+    };
+
+    environment.systemPackages = with pkgs; [ tcpdump ];
+  };
+
+  testScript = lib.readFile ./ulogd.py;
+})
diff --git a/nixpkgs/nixos/tests/ulogd/ulogd.py b/nixpkgs/nixos/tests/ulogd/ulogd.py
new file mode 100644
index 000000000000..76a8d0c6e24a
--- /dev/null
+++ b/nixpkgs/nixos/tests/ulogd/ulogd.py
@@ -0,0 +1,49 @@
+start_all()
+machine.wait_for_unit("ulogd.service")
+machine.systemctl("start network-online.target")
+machine.wait_for_unit("network-online.target")
+
+with subtest("Ulogd is running"):
+    machine.succeed("pgrep ulogd >&2")
+
+# All packets show up twice in the logs
+with subtest("Logs are collected"):
+    machine.succeed("ping -f 127.0.0.1 -c 5 >&2")
+    machine.succeed("sleep 2")
+    machine.wait_until_succeeds("du /var/log/ulogd.pcap")
+    _, echo_request_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 8 and host 127.0.0.1")
+    expected, actual = 5 * 2, len(echo_request_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP request packets from pcap, got: {actual}"
+    _, echo_reply_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 0 and host 127.0.0.1")
+    expected, actual = 5 * 2, len(echo_reply_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP reply packets from pcap, got: {actual}"
+
+    machine.wait_until_succeeds("du /var/log/ulogd_pkts.log")
+    _, echo_request_packets = machine.execute("grep TYPE=8 /var/log/ulogd_pkts.log")
+    expected, actual = 5 * 2, len(echo_request_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP request packets from logfile, got: {actual}"
+    _, echo_reply_packets = machine.execute("grep TYPE=0 /var/log/ulogd_pkts.log")
+    expected, actual = 5 * 2, len(echo_reply_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP reply packets from logfile, got: {actual}"
+
+with subtest("Reloading service reopens log file"):
+    machine.succeed("mv /var/log/ulogd.pcap /var/log/old_ulogd.pcap")
+    machine.succeed("mv /var/log/ulogd_pkts.log /var/log/old_ulogd_pkts.log")
+    machine.succeed("systemctl reload ulogd.service")
+    machine.succeed("ping -f 127.0.0.1 -c 5 >&2")
+    machine.succeed("sleep 2")
+    machine.wait_until_succeeds("du /var/log/ulogd.pcap")
+    _, echo_request_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 8 and host 127.0.0.1")
+    expected, actual = 5 * 2, len(echo_request_packets.splitlines())
+    assert expected == actual, f"Expected {expected} packets, got: {actual}"
+    _, echo_reply_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 0 and host 127.0.0.1")
+    expected, actual = 5 * 2, len(echo_reply_packets.splitlines())
+    assert expected == actual, f"Expected {expected} packets, got: {actual}"
+
+    machine.wait_until_succeeds("du /var/log/ulogd_pkts.log")
+    _, echo_request_packets = machine.execute("grep TYPE=8 /var/log/ulogd_pkts.log")
+    expected, actual = 5 * 2, len(echo_request_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP request packets from logfile, got: {actual}"
+    _, echo_reply_packets = machine.execute("grep TYPE=0 /var/log/ulogd_pkts.log")
+    expected, actual = 5 * 2, len(echo_reply_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP reply packets from logfile, got: {actual}"
diff --git a/nixpkgs/nixos/tests/unbound.nix b/nixpkgs/nixos/tests/unbound.nix
new file mode 100644
index 000000000000..39a01259edeb
--- /dev/null
+++ b/nixpkgs/nixos/tests/unbound.nix
@@ -0,0 +1,315 @@
+/*
+ Test that our unbound module indeed works as most users would expect.
+ There are a few settings that we must consider when modifying the test. The
+ usual use-cases for unbound are
+   * running a recursive DNS resolver on the local machine
+   * running a recursive DNS resolver on the local machine, forwarding to a local DNS server via UDP/53 & TCP/53
+   * running a recursive DNS resolver on the local machine, forwarding to a local DNS server via TCP/853 (DoT)
+   * running a recursive DNS resolver on a machine in the network awaiting input from clients over TCP/53 & UDP/53
+   * running a recursive DNS resolver on a machine in the network awaiting input from clients over TCP/853 (DoT)
+
+ In the below test setup we are trying to implement all of those use cases.
+
+ Another aspect that we cover is access to the local control UNIX socket. It
+ can optionally be enabled and users can optionally be in a group to gain
+ access. Users that are not in the group (except for root) should not have
+ access to that socket. Also, when there is no socket configured, users
+ shouldn't be able to access the control socket at all. Not even root.
+*/
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    # common client configuration that we can just use for the multitude of
+    # clients we are constructing
+    common = { lib, pkgs, ... }: {
+      config = {
+        environment.systemPackages = [ pkgs.knot-dns ];
+
+        # disable the root anchor update as we do not have internet access during
+        # the test execution
+        services.unbound.enableRootTrustAnchor = false;
+
+        # we want to test the full-variant of the package to also get DoH support
+        services.unbound.package = pkgs.unbound-full;
+      };
+    };
+
+    cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+      openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=dns.example.local'
+      mkdir -p $out
+      cp key.pem cert.pem $out
+    '';
+  in
+  {
+    name = "unbound";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ andir ];
+    };
+
+    nodes = {
+
+      # The server that actually serves our zones, this tests unbounds authoriative mode
+      authoritative = { lib, pkgs, config, ... }: {
+        imports = [ common ];
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
+          { address = "192.168.0.1"; prefixLength = 24; }
+        ];
+        networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
+          { address = "fd21::1"; prefixLength = 64; }
+        ];
+        networking.firewall.allowedTCPPorts = [ 53 ];
+        networking.firewall.allowedUDPPorts = [ 53 ];
+
+        services.unbound = {
+          enable = true;
+          settings = {
+            server = {
+              interface = [ "192.168.0.1" "fd21::1" "::1" "127.0.0.1" ];
+              access-control = [ "192.168.0.0/24 allow" "fd21::/64 allow" "::1 allow" "127.0.0.0/8 allow" ];
+              local-data = [
+                ''"example.local. IN A 1.2.3.4"''
+                ''"example.local. IN AAAA abcd::eeff"''
+              ];
+            };
+          };
+        };
+      };
+
+      # The resolver that knows that forwards (only) to the authoritative server
+      # and listens on UDP/53, TCP/53 & TCP/853.
+      resolver = { lib, nodes, ... }: {
+        imports = [ common ];
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
+          { address = "192.168.0.2"; prefixLength = 24; }
+        ];
+        networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
+          { address = "fd21::2"; prefixLength = 64; }
+        ];
+        networking.firewall.allowedTCPPorts = [
+          53 # regular DNS
+          853 # DNS over TLS
+          443 # DNS over HTTPS
+        ];
+        networking.firewall.allowedUDPPorts = [ 53 ];
+
+        services.unbound = {
+          enable = true;
+          settings = {
+            server = {
+              interface = [ "::1" "127.0.0.1" "192.168.0.2" "fd21::2"
+                            "192.168.0.2@853" "fd21::2@853" "::1@853" "127.0.0.1@853"
+                            "192.168.0.2@443" "fd21::2@443" "::1@443" "127.0.0.1@443" ];
+              access-control = [ "192.168.0.0/24 allow" "fd21::/64 allow" "::1 allow" "127.0.0.0/8 allow" ];
+              tls-service-pem = "${cert}/cert.pem";
+              tls-service-key = "${cert}/key.pem";
+            };
+            forward-zone = [
+              {
+                name = ".";
+                forward-addr = [
+                  (lib.head nodes.authoritative.networking.interfaces.eth1.ipv6.addresses).address
+                  (lib.head nodes.authoritative.networking.interfaces.eth1.ipv4.addresses).address
+                ];
+              }
+            ];
+          };
+        };
+      };
+
+      # machine that runs a local unbound that will be reconfigured during test execution
+      local_resolver = { lib, nodes, config, ... }: {
+        imports = [ common ];
+        networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
+          { address = "192.168.0.3"; prefixLength = 24; }
+        ];
+        networking.interfaces.eth1.ipv6.addresses = lib.mkForce [
+          { address = "fd21::3"; prefixLength = 64; }
+        ];
+        networking.firewall.allowedTCPPorts = [
+          53 # regular DNS
+        ];
+        networking.firewall.allowedUDPPorts = [ 53 ];
+
+        services.unbound = {
+          enable = true;
+          settings = {
+            server = {
+              interface = [ "::1" "127.0.0.1" ];
+              access-control = [ "::1 allow" "127.0.0.0/8 allow" ];
+            };
+            include = "/etc/unbound/extra*.conf";
+          };
+          localControlSocketPath = "/run/unbound/unbound.ctl";
+        };
+
+        users.users = {
+          # user that is permitted to access the unix socket
+          someuser = {
+            isSystemUser = true;
+            group = "someuser";
+            extraGroups = [
+              config.users.users.unbound.group
+            ];
+          };
+
+          # user that is not permitted to access the unix socket
+          unauthorizeduser = {
+            isSystemUser = true;
+            group = "unauthorizeduser";
+          };
+
+        };
+        users.groups = {
+          someuser = {};
+          unauthorizeduser = {};
+        };
+
+        # Used for testing configuration reloading
+        environment.etc = {
+          "unbound-extra1.conf".text = ''
+            forward-zone:
+            name: "example.local."
+            forward-addr: ${(lib.head nodes.resolver.networking.interfaces.eth1.ipv6.addresses).address}
+            forward-addr: ${(lib.head nodes.resolver.networking.interfaces.eth1.ipv4.addresses).address}
+          '';
+          "unbound-extra2.conf".text = ''
+            auth-zone:
+              name: something.local.
+              zonefile: ${pkgs.writeText "zone" ''
+                something.local. IN A 3.4.5.6
+              ''}
+          '';
+        };
+      };
+
+
+      # plain node that only has network access and doesn't run any part of the
+      # resolver software locally
+      client = { lib, nodes, ... }: {
+        imports = [ common ];
+        networking.nameservers = [
+          (lib.head nodes.resolver.networking.interfaces.eth1.ipv6.addresses).address
+          (lib.head nodes.resolver.networking.interfaces.eth1.ipv4.addresses).address
+        ];
+        networking.interfaces.eth1.ipv4.addresses = [
+          { address = "192.168.0.10"; prefixLength = 24; }
+        ];
+        networking.interfaces.eth1.ipv6.addresses = [
+          { address = "fd21::10"; prefixLength = 64; }
+        ];
+      };
+    };
+
+    testScript = { nodes, ... }: ''
+      import typing
+
+      zone = "example.local."
+      records = [("AAAA", "abcd::eeff"), ("A", "1.2.3.4")]
+
+
+      def query(
+          machine,
+          host: str,
+          query_type: str,
+          query: str,
+          expected: typing.Optional[str] = None,
+          args: typing.Optional[typing.List[str]] = None,
+      ):
+          """
+          Execute a single query and compare the result with expectation
+          """
+          text_args = ""
+          if args:
+              text_args = " ".join(args)
+
+          out = machine.succeed(
+              f"kdig {text_args} {query} {query_type} @{host} +short"
+          ).strip()
+          machine.log(f"{host} replied with {out}")
+          if expected:
+              assert expected == out, f"Expected `{expected}` but got `{out}`"
+
+
+      def test(machine, remotes, /, doh=False, zone=zone, records=records, args=[]):
+          """
+          Run queries for the given remotes on the given machine.
+          """
+          for query_type, expected in records:
+              for remote in remotes:
+                  query(machine, remote, query_type, zone, expected, args)
+                  query(machine, remote, query_type, zone, expected, ["+tcp"] + args)
+                  if doh:
+                      query(
+                          machine,
+                          remote,
+                          query_type,
+                          zone,
+                          expected,
+                          ["+tcp", "+tls"] + args,
+                      )
+                      query(
+                          machine,
+                          remote,
+                          query_type,
+                          zone,
+                          expected,
+                          ["+https"] + args,
+                      )
+
+
+      client.start()
+      authoritative.wait_for_unit("unbound.service")
+
+      # verify that we can resolve locally
+      with subtest("test the authoritative servers local responses"):
+          test(authoritative, ["::1", "127.0.0.1"])
+
+      resolver.wait_for_unit("unbound.service")
+
+      with subtest("root is unable to use unbounc-control when the socket is not configured"):
+          resolver.succeed("which unbound-control")  # the binary must exist
+          resolver.fail("unbound-control list_forwards")  # the invocation must fail
+
+      # verify that the resolver is able to resolve on all the local protocols
+      with subtest("test that the resolver resolves on all protocols and transports"):
+          test(resolver, ["::1", "127.0.0.1"], doh=True)
+
+      resolver.wait_for_unit("multi-user.target")
+
+      with subtest("client should be able to query the resolver"):
+          test(client, ["${(lib.head nodes.resolver.networking.interfaces.eth1.ipv6.addresses).address}", "${(lib.head nodes.resolver.networking.interfaces.eth1.ipv4.addresses).address}"], doh=True)
+
+      # discard the client we do not need anymore
+      client.shutdown()
+
+      local_resolver.wait_for_unit("multi-user.target")
+
+      # link a new config file to /etc/unbound/extra.conf
+      local_resolver.succeed("ln -s /etc/unbound-extra1.conf /etc/unbound/extra1.conf")
+
+      # reload the server & ensure the forwarding works
+      with subtest("test that the local resolver resolves on all protocols and transports"):
+          local_resolver.succeed("systemctl reload unbound")
+          print(local_resolver.succeed("journalctl -u unbound -n 1000"))
+          test(local_resolver, ["::1", "127.0.0.1"], args=["+timeout=60"])
+
+      with subtest("test that we can use the unbound control socket"):
+          out = local_resolver.succeed(
+              "sudo -u someuser -- unbound-control list_forwards"
+          ).strip()
+
+          # Thank you black! Can't really break this line into a readable version.
+          expected = "example.local. IN forward ${(lib.head nodes.resolver.networking.interfaces.eth1.ipv6.addresses).address} ${(lib.head nodes.resolver.networking.interfaces.eth1.ipv4.addresses).address}"
+          assert out == expected, f"Expected `{expected}` but got `{out}` instead."
+          local_resolver.fail("sudo -u unauthorizeduser -- unbound-control list_forwards")
+
+
+      # link a new config file to /etc/unbound/extra.conf
+      local_resolver.succeed("ln -sf /etc/unbound-extra2.conf /etc/unbound/extra2.conf")
+
+      # reload the server & ensure the new local zone works
+      with subtest("test that we can query the new local zone"):
+          local_resolver.succeed("unbound-control reload")
+          r = [("A", "3.4.5.6")]
+          test(local_resolver, ["::1", "127.0.0.1"], zone="something.local.", records=r)
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/unifi.nix b/nixpkgs/nixos/tests/unifi.nix
new file mode 100644
index 000000000000..d371bafd6965
--- /dev/null
+++ b/nixpkgs/nixos/tests/unifi.nix
@@ -0,0 +1,38 @@
+# Test UniFi controller
+
+{ system ? builtins.currentSystem
+, config ? { allowUnfree = true; }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  makeAppTest = unifi: makeTest {
+    name = "unifi-controller-${unifi.version}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ patryk27 zhaofengli ];
+    };
+
+    nodes.server = {
+      nixpkgs.config = config;
+
+      services.unifi = {
+        enable = true;
+        unifiPackage = unifi;
+        openFirewall = false;
+      };
+    };
+
+    testScript = ''
+      server.wait_for_unit("unifi.service")
+      server.wait_until_succeeds("curl -Lk https://localhost:8443 >&2", timeout=300)
+    '';
+  };
+in with pkgs; {
+  unifiLTS = makeAppTest unifiLTS;
+  unifi5 = makeAppTest unifi5;
+  unifi6 = makeAppTest unifi6;
+  unifi7 = makeAppTest unifi7;
+}
diff --git a/nixpkgs/nixos/tests/upnp.nix b/nixpkgs/nixos/tests/upnp.nix
new file mode 100644
index 000000000000..93bc08f752ce
--- /dev/null
+++ b/nixpkgs/nixos/tests/upnp.nix
@@ -0,0 +1,99 @@
+# This tests whether UPnP port mappings can be created using Miniupnpd
+# and Miniupnpc.
+# It runs a Miniupnpd service on one machine, and verifies
+# a client can indeed create a port mapping using Miniupnpc. If
+# this succeeds an external client will try to connect to the port
+# mapping.
+
+import ./make-test-python.nix ({ pkgs, useNftables, ... }:
+
+let
+  internalRouterAddress = "192.168.3.1";
+  internalClient1Address = "192.168.3.2";
+  externalRouterAddress = "80.100.100.1";
+  externalClient2Address = "80.100.100.2";
+in
+{
+  name = "upnp";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ bobvanderlinden ];
+  };
+
+  nodes =
+    {
+      router =
+        { pkgs, nodes, ... }:
+        { virtualisation.vlans = [ 1 2 ];
+          networking.nat.enable = true;
+          networking.nat.internalInterfaces = [ "eth2" ];
+          networking.nat.externalInterface = "eth1";
+          networking.nftables.enable = useNftables;
+          networking.firewall.enable = true;
+          networking.firewall.trustedInterfaces = [ "eth2" ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalRouterAddress; prefixLength = 24; }
+          ];
+          networking.interfaces.eth2.ipv4.addresses = [
+            { address = internalRouterAddress; prefixLength = 24; }
+          ];
+          services.miniupnpd = {
+            enable = true;
+            externalInterface = "eth1";
+            internalIPs = [ "eth2" ];
+            appendConfig = ''
+              ext_ip=${externalRouterAddress}
+            '';
+          };
+        };
+
+      client1 =
+        { pkgs, nodes, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpc pkgs.netcat ];
+          virtualisation.vlans = [ 2 ];
+          networking.defaultGateway = internalRouterAddress;
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = internalClient1Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+
+          services.httpd.enable = true;
+          services.httpd.virtualHosts.localhost = {
+            listen = [{ ip = "*"; port = 9000; }];
+            adminAddr = "foo@example.org";
+            documentRoot = "/tmp";
+          };
+        };
+
+      client2 =
+        { pkgs, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpc ];
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalClient2Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      start_all()
+
+      # Wait for network and miniupnpd.
+      router.systemctl("start network-online.target")
+      router.wait_for_unit("network-online.target")
+      # $router.wait_for_unit("nat")
+      router.wait_for_unit("${if useNftables then "nftables" else "firewall"}.service")
+      router.wait_for_unit("miniupnpd")
+
+      client1.systemctl("start network-online.target")
+      client1.wait_for_unit("network-online.target")
+
+      client1.succeed("upnpc -a ${internalClient1Address} 9000 9000 TCP")
+
+      client1.wait_for_unit("httpd")
+      client2.wait_until_succeeds("curl -f http://${externalRouterAddress}:9000/")
+    '';
+
+})
diff --git a/nixpkgs/nixos/tests/uptermd.nix b/nixpkgs/nixos/tests/uptermd.nix
new file mode 100644
index 000000000000..469aa5047c27
--- /dev/null
+++ b/nixpkgs/nixos/tests/uptermd.nix
@@ -0,0 +1,66 @@
+import ./make-test-python.nix ({ pkgs, ...}:
+
+let
+  client = {pkgs, ...}:{
+    environment.systemPackages = [ pkgs.upterm ];
+  };
+in
+{
+  name = "uptermd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fleaz ];
+  };
+
+  nodes = {
+    server = {config, ...}: {
+      services.uptermd = {
+        enable = true;
+        openFirewall = true;
+        port = 1337;
+      };
+    };
+    client1 = client;
+    client2 = client;
+  };
+
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("uptermd.service")
+    server.systemctl("start network-online.target")
+    server.wait_for_unit("network-online.target")
+
+    # wait for upterm port to be reachable
+    client1.wait_until_succeeds("nc -z -v server 1337")
+
+    # Add SSH hostkeys from the server to both clients
+    # uptermd needs an '@cert-authority entry so we need to modify the known_hosts file
+    client1.execute("mkdir -p ~/.ssh && ssh -o StrictHostKeyChecking=no -p 1337 server ls")
+    client1.execute("echo @cert-authority $(cat ~/.ssh/known_hosts) > ~/.ssh/known_hosts")
+    client2.execute("mkdir -p ~/.ssh && ssh -o StrictHostKeyChecking=no -p 1337 server ls")
+    client2.execute("echo @cert-authority $(cat ~/.ssh/known_hosts) > ~/.ssh/known_hosts")
+
+    client1.wait_for_unit("multi-user.target")
+    client1.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    client1.wait_until_tty_matches("1", "login: ")
+    client1.send_chars("root\n")
+    client1.wait_until_succeeds("pgrep -u root bash")
+
+    client1.execute("ssh-keygen -t ed25519 -N \"\" -f /root/.ssh/id_ed25519")
+    client1.send_chars("TERM=xterm upterm host --server ssh://server:1337 --force-command hostname -- bash > /tmp/session-details\n")
+    client1.wait_for_file("/tmp/session-details")
+    client1.send_key("q")
+
+    # uptermd can't connect if we don't have a keypair
+    client2.execute("ssh-keygen -t ed25519 -N \"\" -f /root/.ssh/id_ed25519")
+
+    # Grep the ssh connect command from the output of 'upterm host'
+    ssh_command = client1.succeed("grep 'SSH Session' /tmp/session-details | cut -d':' -f2-").strip()
+
+    # Connect with client2. Because we used '--force-command hostname' we should get "client1" as the output
+    output = client2.succeed(ssh_command)
+
+    assert output.strip() == "client1"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/uptime-kuma.nix b/nixpkgs/nixos/tests/uptime-kuma.nix
new file mode 100644
index 000000000000..00e2008a5257
--- /dev/null
+++ b/nixpkgs/nixos/tests/uptime-kuma.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+{
+  name = "uptime-kuma";
+  meta.maintainers = with lib.maintainers; [ julienmalka ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.uptime-kuma.enable = true; };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("uptime-kuma.service")
+    machine.wait_for_open_port(3001)
+    machine.succeed("curl --fail http://localhost:3001/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/urn-timer.nix b/nixpkgs/nixos/tests/urn-timer.nix
new file mode 100644
index 000000000000..10c5bef49b5b
--- /dev/null
+++ b/nixpkgs/nixos/tests/urn-timer.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "urn-timer";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.urn-timer ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("urn-gtk ${pkgs.urn-timer.src}/splits_examples/sotn.json >&2 &")
+      machine.wait_for_window("urn")
+      machine.wait_for_text(r"(Mist|Bat|Reverse|Dracula)")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/usbguard.nix b/nixpkgs/nixos/tests/usbguard.nix
new file mode 100644
index 000000000000..d6d3a80c5d23
--- /dev/null
+++ b/nixpkgs/nixos/tests/usbguard.nix
@@ -0,0 +1,62 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "usbguard";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tnias ];
+  };
+
+  nodes.machine =
+    { ... }:
+    {
+      services.usbguard = {
+        enable = true;
+        IPCAllowedUsers = [ "alice" "root" ];
+
+        # As virtual USB devices get attached to the "QEMU USB Hub" we need to
+        # allow Hubs. Otherwise we would have to explicitly allow them too.
+        rules = ''
+          allow with-interface equals { 09:00:00 }
+        '';
+      };
+      imports = [ ./common/user-account.nix ];
+    };
+
+  testScript = ''
+    # create a blank disk image for our fake USB stick
+    with open(machine.state_dir / "usbstick.img", "wb") as stick:
+        stick.write(b"\x00" * (1024 * 1024))
+
+    # wait for machine to have started and the usbguard service to be up
+    machine.wait_for_unit("usbguard.service")
+
+    with subtest("IPC access control"):
+        # User "alice" is allowed to access the IPC interface
+        machine.succeed("su alice -c 'usbguard list-devices'")
+
+        # User "bob" is not allowed to access the IPC interface
+        machine.fail("su bob -c 'usbguard list-devices'")
+
+    with subtest("check basic functionality"):
+        # at this point we expect that no USB HDD is connected
+        machine.fail("usbguard list-devices | grep -E 'QEMU USB HARDDRIVE'")
+
+        # insert usb device
+        machine.send_monitor_command(
+            f"drive_add 0 id=stick,if=none,file={stick.name},format=raw"
+        )
+        machine.send_monitor_command("device_add usb-storage,id=stick,drive=stick")
+
+        # the attached USB HDD should show up after a short while
+        machine.wait_until_succeeds("usbguard list-devices | grep -E 'QEMU USB HARDDRIVE'")
+
+        # at this point there should be a **blocked** USB HDD
+        machine.succeed("usbguard list-devices | grep -E 'block.*QEMU USB HARDDRIVE'")
+        machine.fail("usbguard list-devices | grep -E ' allow .*QEMU USB HARDDRIVE'")
+
+        # allow storage devices
+        machine.succeed("usbguard allow-device 'with-interface { 08:*:* }'")
+
+        # at this point there should be an **allowed** USB HDD
+        machine.succeed("usbguard list-devices | grep -E ' allow .*QEMU USB HARDDRIVE'")
+        machine.fail("usbguard list-devices | grep -E ' block .*QEMU USB HARDDRIVE'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/user-activation-scripts.nix b/nixpkgs/nixos/tests/user-activation-scripts.nix
new file mode 100644
index 000000000000..ebd96b019e92
--- /dev/null
+++ b/nixpkgs/nixos/tests/user-activation-scripts.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "user-activation-scripts";
+  meta = with lib.maintainers; { maintainers = [ chkno ]; };
+
+  nodes.machine = {
+    system.userActivationScripts.foo = "mktemp ~/user-activation-ran.XXXXXX";
+    users.users.alice = {
+      initialPassword = "pass1";
+      isNormalUser = true;
+    };
+    systemd.user.tmpfiles.users.alice.rules = [ "r %h/file-to-remove" ];
+  };
+
+  testScript = ''
+    def verify_user_activation_run_count(n):
+        machine.succeed(
+            '[[ "$(find /home/alice/ -name user-activation-ran.\\* | wc -l)" == %s ]]' % n
+        )
+
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("getty@tty1.service")
+    machine.wait_until_tty_matches("1", "login: ")
+    machine.send_chars("alice\n")
+    machine.wait_until_tty_matches("1", "Password: ")
+    machine.send_chars("pass1\n")
+    machine.send_chars("touch login-ok\n")
+    machine.wait_for_file("/home/alice/login-ok")
+    verify_user_activation_run_count(1)
+
+    machine.succeed("touch /home/alice/file-to-remove")
+    machine.succeed("/run/current-system/bin/switch-to-configuration test")
+    verify_user_activation_run_count(2)
+    machine.succeed("[[ ! -f /home/alice/file-to-remove ]] || false")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/user-expiry.nix b/nixpkgs/nixos/tests/user-expiry.nix
new file mode 100644
index 000000000000..bcaed7a0ccb0
--- /dev/null
+++ b/nixpkgs/nixos/tests/user-expiry.nix
@@ -0,0 +1,70 @@
+let
+  alice = "alice";
+  bob = "bob";
+  eve = "eve";
+  passwd = "pass1";
+in
+{
+  name = "user-expiry";
+
+  nodes = {
+    machine = {
+      users.users = {
+        ${alice} = {
+          initialPassword = passwd;
+          isNormalUser = true;
+          expires = "1990-01-01";
+        };
+        ${bob} = {
+          initialPassword = passwd;
+          isNormalUser = true;
+          expires = "2990-01-01";
+        };
+        ${eve} = {
+          initialPassword = passwd;
+          isNormalUser = true;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    def switch_to_tty(tty_number):
+      machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'")
+      machine.send_key(f"alt-f{tty_number}")
+      machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]")
+      machine.wait_for_unit(f"getty@tty{tty_number}.service")
+      machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'")
+
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("getty@tty1.service")
+
+    with subtest("${alice} cannot login"):
+      machine.wait_until_tty_matches("1", "login: ")
+      machine.send_chars("${alice}\n")
+      machine.wait_until_tty_matches("1", "Password: ")
+      machine.send_chars("${passwd}\n")
+
+      machine.wait_until_succeeds("journalctl --grep='account ${alice} has expired \\(account expired\\)'")
+      machine.wait_until_tty_matches("1", "login: ")
+
+    with subtest("${bob} can login"):
+      switch_to_tty(2)
+      machine.wait_until_tty_matches("2", "login: ")
+      machine.send_chars("${bob}\n")
+      machine.wait_until_tty_matches("2", "Password: ")
+      machine.send_chars("${passwd}\n")
+
+      machine.wait_until_succeeds("pgrep -u ${bob} bash")
+
+    with subtest("${eve} can login"):
+      switch_to_tty(3)
+      machine.wait_until_tty_matches("3", "login: ")
+      machine.send_chars("${eve}\n")
+      machine.wait_until_tty_matches("3", "Password: ")
+      machine.send_chars("${passwd}\n")
+
+      machine.wait_until_succeeds("pgrep -u ${eve} bash")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/user-home-mode.nix b/nixpkgs/nixos/tests/user-home-mode.nix
new file mode 100644
index 000000000000..070cb0b75cc9
--- /dev/null
+++ b/nixpkgs/nixos/tests/user-home-mode.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "user-home-mode";
+  meta = with lib.maintainers; { maintainers = [ fbeffa ]; };
+
+  nodes.machine = {
+    users.users.alice = {
+      initialPassword = "pass1";
+      isNormalUser = true;
+    };
+    users.users.bob = {
+      initialPassword = "pass2";
+      isNormalUser = true;
+      homeMode = "750";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("getty@tty1.service")
+    machine.wait_until_tty_matches("1", "login: ")
+    machine.send_chars("alice\n")
+    machine.wait_until_tty_matches("1", "Password: ")
+    machine.send_chars("pass1\n")
+    machine.succeed('[ "$(stat -c %a /home/alice)" == "700" ]')
+    machine.succeed('[ "$(stat -c %a /home/bob)" == "750" ]')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/uwsgi.nix b/nixpkgs/nixos/tests/uwsgi.nix
new file mode 100644
index 000000000000..62da9e0a7168
--- /dev/null
+++ b/nixpkgs/nixos/tests/uwsgi.nix
@@ -0,0 +1,81 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "uwsgi";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lnl7 ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    users.users.hello  =
+      { isSystemUser = true;
+        group = "hello";
+      };
+    users.groups.hello = { };
+
+    services.uwsgi = {
+      enable = true;
+      plugins = [ "python3" "php" ];
+      capabilities = [ "CAP_NET_BIND_SERVICE" ];
+      instance.type = "emperor";
+
+      instance.vassals.hello = {
+        type = "normal";
+        immediate-uid = "hello";
+        immediate-gid = "hello";
+        module = "wsgi:application";
+        http = ":80";
+        cap = "net_bind_service";
+        pythonPackages = self: [ self.flask ];
+        chdir = pkgs.writeTextDir "wsgi.py" ''
+          from flask import Flask
+          import subprocess
+          application = Flask(__name__)
+
+          @application.route("/")
+          def hello():
+              return "Hello, World!"
+
+          @application.route("/whoami")
+          def whoami():
+              whoami = "${pkgs.coreutils}/bin/whoami"
+              proc = subprocess.run(whoami, capture_output=True)
+              return proc.stdout.decode().strip()
+        '';
+      };
+
+      instance.vassals.php = {
+        type = "normal";
+        master = true;
+        workers = 2;
+        http-socket = ":8000";
+        http-socket-modifier1 = 14;
+        php-index = "index.php";
+        php-docroot = pkgs.writeTextDir "index.php" ''
+          <?php echo "Hello World\n"; ?>
+        '';
+      };
+    };
+  };
+
+  testScript =
+    ''
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("uwsgi.service")
+
+      with subtest("uWSGI has started"):
+          machine.wait_for_unit("uwsgi.service")
+
+      with subtest("Vassal can bind on port <1024"):
+          machine.wait_for_open_port(80)
+          hello = machine.succeed("curl -f http://machine").strip()
+          assert "Hello, World!" in hello, f"Excepted 'Hello, World!', got '{hello}'"
+
+      with subtest("Vassal is running as dedicated user"):
+          username = machine.succeed("curl -f http://machine/whoami").strip()
+          assert username == "hello", f"Excepted 'hello', got '{username}'"
+
+      with subtest("PHP plugin is working"):
+          machine.wait_for_open_port(8000)
+          assert "Hello World" in machine.succeed("curl -fv http://machine:8000")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/v2ray.nix b/nixpkgs/nixos/tests/v2ray.nix
new file mode 100644
index 000000000000..9eee962c64e4
--- /dev/null
+++ b/nixpkgs/nixos/tests/v2ray.nix
@@ -0,0 +1,91 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: let
+
+  v2rayUser = {
+    # A random UUID.
+    id = "a6a46834-2150-45f8-8364-0f6f6ab32384";
+    alterId = 0; # Non-zero support will be disabled in the future.
+  };
+
+  # 1080 [http proxy] -> 1081 [vmess] -> direct
+  v2rayConfig = {
+    inbounds = [
+      {
+        tag = "http_in";
+        port = 1080;
+        listen = "127.0.0.1";
+        protocol = "http";
+      }
+      {
+        tag = "vmess_in";
+        port = 1081;
+        listen = "127.0.0.1";
+        protocol = "vmess";
+        settings.clients = [ v2rayUser ];
+      }
+    ];
+    outbounds = [
+      {
+        tag = "vmess_out";
+        protocol = "vmess";
+        settings.vnext = [{
+          address = "127.0.0.1";
+          port = 1081;
+          users = [ v2rayUser ];
+        }];
+      }
+      {
+        tag = "direct";
+        protocol = "freedom";
+      }
+    ];
+    routing.rules = [
+      {
+        type = "field";
+        inboundTag = "http_in";
+        outboundTag = "vmess_out";
+      }
+      {
+        type = "field";
+        inboundTag = "vmess_in";
+        outboundTag = "direct";
+      }
+
+      # Assert assets "geoip" and "geosite" are accessible.
+      {
+        type = "field";
+        ip = [ "geoip:private" ];
+        domain = [ "geosite:category-ads" ];
+        outboundTag = "direct";
+      }
+    ];
+  };
+
+in {
+  name = "v2ray";
+  meta = with lib.maintainers; {
+    maintainers = [ servalcatty ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.v2ray = {
+      enable = true;
+      config = v2rayConfig;
+    };
+    services.httpd = {
+      enable = true;
+      adminAddr = "foo@example.org";
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("httpd.service")
+    machine.wait_for_unit("v2ray.service")
+    machine.wait_for_open_port(80)
+    machine.wait_for_open_port(1080)
+    machine.succeed(
+        "curl --fail --max-time 10 --proxy http://localhost:1080 http://localhost"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/varnish.nix b/nixpkgs/nixos/tests/varnish.nix
new file mode 100644
index 000000000000..76cea1ada547
--- /dev/null
+++ b/nixpkgs/nixos/tests/varnish.nix
@@ -0,0 +1,55 @@
+{
+  system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; }
+, package
+}:
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+  testPath = pkgs.hello;
+in {
+  name = "varnish";
+  meta = {
+    maintainers = lib.teams.helsinki-systems.members;
+  };
+
+  nodes = {
+    varnish = { config, pkgs, ... }: {
+        services.nix-serve = {
+          enable = true;
+        };
+
+        services.varnish = {
+          inherit package;
+          enable = true;
+          http_address = "0.0.0.0:80";
+          config = ''
+            vcl 4.0;
+
+            backend nix-serve {
+              .host = "127.0.0.1";
+              .port = "${toString config.services.nix-serve.port}";
+            }
+          '';
+        };
+
+        networking.firewall.allowedTCPPorts = [ 80 ];
+        system.extraDependencies = [ testPath ];
+      };
+
+    client = { lib, ... }: {
+      nix.settings = {
+        require-sigs = false;
+        substituters = lib.mkForce [ "http://varnish" ];
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    varnish.wait_for_open_port(80)
+
+    client.wait_until_succeeds("curl -f http://varnish/nix-cache-info");
+
+    client.wait_until_succeeds("nix-store -r ${testPath}");
+    client.succeed("${testPath}/bin/hello");
+  '';
+})
diff --git a/nixpkgs/nixos/tests/vault-agent.nix b/nixpkgs/nixos/tests/vault-agent.nix
new file mode 100644
index 000000000000..dc86c829b67a
--- /dev/null
+++ b/nixpkgs/nixos/tests/vault-agent.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "vault-agent";
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.vault-agent.instances.example.settings = {
+      vault.address = config.environment.variables.VAULT_ADDR;
+
+      auto_auth = [{
+        method = [{
+          type = "token_file";
+          config.token_file_path = pkgs.writeText "vault-token" config.environment.variables.VAULT_TOKEN;
+        }];
+      }];
+
+      template = [{
+        contents = ''
+          {{- with secret "secret/example" }}
+          {{ .Data.data.key }}"
+          {{- end }}
+        '';
+        perms = "0600";
+        destination = "/example";
+      }];
+    };
+
+    services.vault = {
+      enable = true;
+      dev = true;
+      devRootTokenID = config.environment.variables.VAULT_TOKEN;
+    };
+
+    environment = {
+      systemPackages = [ pkgs.vault ];
+      variables = {
+        VAULT_ADDR = "http://localhost:8200";
+        VAULT_TOKEN = "root";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("vault.service")
+    machine.wait_for_open_port(8200)
+
+    machine.wait_until_succeeds('vault kv put secret/example key=example')
+
+    machine.wait_for_unit("vault-agent-example.service")
+
+    machine.wait_for_file("/example")
+    machine.succeed('grep "example" /example')
+  '';
+})
diff --git a/nixpkgs/nixos/tests/vault-dev.nix b/nixpkgs/nixos/tests/vault-dev.nix
new file mode 100644
index 000000000000..ba9a1015cc13
--- /dev/null
+++ b/nixpkgs/nixos/tests/vault-dev.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "vault-dev";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lnl7 mic92 ];
+  };
+  nodes.machine = { pkgs, config, ... }: {
+    environment.systemPackages = [ pkgs.vault ];
+    environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
+    environment.variables.VAULT_TOKEN = "phony-secret";
+
+    services.vault = {
+      enable = true;
+      dev = true;
+      devRootTokenID = config.environment.variables.VAULT_TOKEN;
+    };
+  };
+
+  testScript = ''
+    import json
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("vault.service")
+    machine.wait_for_open_port(8200)
+    out = machine.succeed("vault status -format=json")
+    print(out)
+    status = json.loads(out)
+    assert status.get("initialized") == True
+    machine.succeed("vault kv put secret/foo bar=baz")
+    out = machine.succeed("vault kv get -format=json secret/foo")
+    print(out)
+    status = json.loads(out)
+    assert status.get("data", {}).get("data", {}).get("bar") == "baz"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/vault-postgresql.nix b/nixpkgs/nixos/tests/vault-postgresql.nix
new file mode 100644
index 000000000000..e0e5881c6da7
--- /dev/null
+++ b/nixpkgs/nixos/tests/vault-postgresql.nix
@@ -0,0 +1,69 @@
+/* This test checks that
+    - multiple config files can be loaded
+    - the storage backend can be in a file outside the nix store
+      as is required for security (required because while confidentiality is
+      always covered, availability isn't)
+    - the postgres integration works
+ */
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "vault-postgresql";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lnl7 roberth ];
+  };
+  nodes.machine = { lib, pkgs, ... }: {
+    environment.systemPackages = [ pkgs.vault ];
+    environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
+    services.vault.enable = true;
+    services.vault.extraSettingsPaths = [ "/run/vault.hcl" ];
+
+    systemd.services.vault = {
+      after = [
+        "postgresql.service"
+      ];
+      # Try for about 10 minutes rather than the default of 5 attempts.
+      serviceConfig.RestartSec = 1;
+      serviceConfig.StartLimitBurst = 600;
+    };
+    # systemd.services.vault.unitConfig.RequiresMountsFor = "/run/keys/";
+
+    services.postgresql.enable = true;
+    services.postgresql.initialScript = pkgs.writeText "init.psql" ''
+      CREATE USER vaultuser WITH ENCRYPTED PASSWORD 'thisisthepass';
+      GRANT CONNECT ON DATABASE postgres TO vaultuser;
+
+      -- https://www.vaultproject.io/docs/configuration/storage/postgresql
+      CREATE TABLE vault_kv_store (
+        parent_path TEXT COLLATE "C" NOT NULL,
+        path        TEXT COLLATE "C",
+        key         TEXT COLLATE "C",
+        value       BYTEA,
+        CONSTRAINT pkey PRIMARY KEY (path, key)
+      );
+      CREATE INDEX parent_path_idx ON vault_kv_store (parent_path);
+
+      GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO vaultuser;
+    '';
+  };
+
+  testScript =
+    ''
+      secretConfig = """
+          storage "postgresql" {
+            connection_url = "postgres://vaultuser:thisisthepass@localhost/postgres?sslmode=disable"
+          }
+          """
+
+      start_all()
+
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("cat >/root/vault.hcl <<EOF\n%s\nEOF\n" % secretConfig)
+      machine.succeed(
+          "install --owner vault --mode 0400 /root/vault.hcl /run/vault.hcl; rm /root/vault.hcl"
+      )
+      machine.wait_for_unit("vault.service")
+      machine.wait_for_open_port(8200)
+      machine.succeed("vault operator init")
+      machine.succeed("vault status || test $? -eq 2")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/vault.nix b/nixpkgs/nixos/tests/vault.nix
new file mode 100644
index 000000000000..1b0a26a4487f
--- /dev/null
+++ b/nixpkgs/nixos/tests/vault.nix
@@ -0,0 +1,25 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "vault";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lnl7 ];
+  };
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.vault ];
+    environment.variables.VAULT_ADDR = "http://127.0.0.1:8200";
+    services.vault.enable = true;
+  };
+
+  testScript =
+    ''
+      start_all()
+
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("vault.service")
+      machine.wait_for_open_port(8200)
+      machine.succeed("vault operator init")
+      # vault now returns exit code 2 for sealed vaults
+      machine.fail("vault status")
+      machine.succeed("vault status || test $? -eq 2")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/vaultwarden.nix b/nixpkgs/nixos/tests/vaultwarden.nix
new file mode 100644
index 000000000000..9d2f0e6ab060
--- /dev/null
+++ b/nixpkgs/nixos/tests/vaultwarden.nix
@@ -0,0 +1,198 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+# These tests will:
+#  * Set up a vaultwarden server
+#  * Have Firefox use the web vault to create an account, log in, and save a password to the valut
+#  * Have the bw cli log in and read that password from the vault
+#
+# Note that Firefox must be on the same machine as the server for WebCrypto APIs to be available (or HTTPS must be configured)
+#
+# The same tests should work without modification on the official bitwarden server, if we ever package that.
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+let
+  backends = [ "sqlite" "mysql" "postgresql" ];
+
+  dbPassword = "please_dont_hack";
+
+  userEmail = "meow@example.com";
+  userPassword = "also_super_secret_ZJWpBKZi668QGt"; # Must be complex to avoid interstitial warning on the signup page
+
+  storedPassword = "seeeecret";
+
+  makeVaultwardenTest = backend: makeTest {
+    name = "vaultwarden-${backend}";
+    meta = {
+      maintainers = with pkgs.lib.maintainers; [ jjjollyjim ];
+    };
+
+    nodes = {
+      server = { pkgs, ... }:
+        let backendConfig = {
+          mysql = {
+            services.mysql = {
+              enable = true;
+              initialScript = pkgs.writeText "mysql-init.sql" ''
+                CREATE DATABASE bitwarden;
+                CREATE USER 'bitwardenuser'@'localhost' IDENTIFIED BY '${dbPassword}';
+                GRANT ALL ON `bitwarden`.* TO 'bitwardenuser'@'localhost';
+                FLUSH PRIVILEGES;
+              '';
+              package = pkgs.mariadb;
+            };
+
+            services.vaultwarden.config.databaseUrl = "mysql://bitwardenuser:${dbPassword}@localhost/bitwarden";
+
+            systemd.services.vaultwarden.after = [ "mysql.service" ];
+          };
+
+          postgresql = {
+            services.postgresql = {
+              enable = true;
+              initialScript = pkgs.writeText "postgresql-init.sql" ''
+                CREATE USER bitwardenuser WITH PASSWORD '${dbPassword}';
+                CREATE DATABASE bitwarden WITH OWNER bitwardenuser;
+              '';
+            };
+
+            services.vaultwarden.config.databaseUrl = "postgresql://bitwardenuser:${dbPassword}@localhost/bitwarden";
+
+            systemd.services.vaultwarden.after = [ "postgresql.service" ];
+          };
+
+          sqlite = { };
+        };
+        in
+        mkMerge [
+          backendConfig.${backend}
+          {
+            services.vaultwarden = {
+              enable = true;
+              dbBackend = backend;
+              config = {
+                rocketAddress = "0.0.0.0";
+                rocketPort = 80;
+              };
+            };
+
+            networking.firewall.allowedTCPPorts = [ 80 ];
+
+            environment.systemPackages =
+              let
+                testRunner = pkgs.writers.writePython3Bin "test-runner"
+                  {
+                    libraries = [ pkgs.python3Packages.selenium ];
+                    flakeIgnore = [
+                      "E501"
+                    ];
+                  } ''
+
+                  from selenium.webdriver.common.by import By
+                  from selenium.webdriver import Firefox
+                  from selenium.webdriver.firefox.options import Options
+                  from selenium.webdriver.support.ui import WebDriverWait
+                  from selenium.webdriver.support import expected_conditions as EC
+
+                  options = Options()
+                  options.add_argument('--headless')
+                  driver = Firefox(options=options)
+
+                  driver.implicitly_wait(20)
+                  driver.get('http://localhost/#/register')
+
+                  wait = WebDriverWait(driver, 10)
+
+                  wait.until(EC.title_contains("Create account"))
+
+                  driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_email').send_keys(
+                      '${userEmail}'
+                  )
+                  driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_name').send_keys(
+                      'A Cat'
+                  )
+                  driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_master-password').send_keys(
+                      '${userPassword}'
+                  )
+                  driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_confirm-master-password').send_keys(
+                      '${userPassword}'
+                  )
+                  if driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').is_selected():
+                      driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').click()
+
+                  driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click()
+
+                  wait.until_not(EC.title_contains("Create account"))
+
+                  driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click()
+
+                  driver.find_element(By.CSS_SELECTOR, 'input#login_input_master-password').send_keys(
+                      '${userPassword}'
+                  )
+                  driver.find_element(By.XPATH, "//button[contains(., 'Log in')]").click()
+
+                  wait.until(EC.title_contains("Vaults"))
+
+                  driver.find_element(By.XPATH, "//button[contains(., 'New item')]").click()
+
+                  driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys(
+                      'secrets'
+                  )
+                  driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys(
+                      '${storedPassword}'
+                  )
+
+                  driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click()
+                '';
+              in
+              [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
+
+          }
+        ];
+
+      client = { pkgs, ... }:
+        {
+          environment.systemPackages = [ pkgs.bitwarden-cli ];
+        };
+    };
+
+    testScript = ''
+      start_all()
+      server.wait_for_unit("vaultwarden.service")
+      server.wait_for_open_port(80)
+
+      with subtest("configure the cli"):
+          client.succeed("bw --nointeraction config server http://server")
+
+      with subtest("can't login to nonexistent account"):
+          client.fail(
+              "bw --nointeraction --raw login ${userEmail} ${userPassword}"
+          )
+
+      with subtest("use the web interface to sign up, log in, and save a password"):
+          server.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner")
+
+      with subtest("log in with the cli"):
+          key = client.succeed(
+              "bw --nointeraction --raw login ${userEmail} ${userPassword}"
+          ).strip()
+
+      with subtest("sync with the cli"):
+          client.succeed(f"bw --nointeraction --raw --session {key} sync -f")
+
+      with subtest("get the password with the cli"):
+          password = client.succeed(
+              f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password"
+          )
+          assert password.strip() == "${storedPassword}"
+    '';
+  };
+in
+builtins.listToAttrs (
+  map
+    (backend: { name = backend; value = makeVaultwardenTest backend; })
+    backends
+)
diff --git a/nixpkgs/nixos/tests/vector.nix b/nixpkgs/nixos/tests/vector.nix
new file mode 100644
index 000000000000..a55eb4e012c5
--- /dev/null
+++ b/nixpkgs/nixos/tests/vector.nix
@@ -0,0 +1,37 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  test1 = makeTest {
+    name = "vector-test1";
+    meta.maintainers = [ pkgs.lib.maintainers.happysalada ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.vector = {
+        enable = true;
+        journaldAccess = true;
+        settings = {
+          sources.journald.type = "journald";
+
+          sinks = {
+            file = {
+              type = "file";
+              inputs = [ "journald" ];
+              path = "/var/lib/vector/logs.log";
+              encoding = { codec = "json"; };
+            };
+          };
+        };
+      };
+    };
+
+    # ensure vector is forwarding the messages appropriately
+    testScript = ''
+      machine.wait_for_unit("vector.service")
+      machine.wait_for_file("/var/lib/vector/logs.log")
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/vengi-tools.nix b/nixpkgs/nixos/tests/vengi-tools.nix
new file mode 100644
index 000000000000..fd7567991487
--- /dev/null
+++ b/nixpkgs/nixos/tests/vengi-tools.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "vengi-tools";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.vengi-tools ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("vengi-voxedit >&2 &")
+      machine.wait_for_window("voxedit")
+      # Let the window load fully
+      machine.sleep(15)
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/victoriametrics.nix b/nixpkgs/nixos/tests/victoriametrics.nix
new file mode 100644
index 000000000000..5e364b67bf87
--- /dev/null
+++ b/nixpkgs/nixos/tests/victoriametrics.nix
@@ -0,0 +1,33 @@
+# This test runs influxdb and checks if influxdb is up and running
+
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "victoriametrics";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ yorickvp ];
+  };
+
+  nodes = {
+    one = { ... }: {
+      services.victoriametrics.enable = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    one.wait_for_unit("victoriametrics.service")
+
+    # write some points and run simple query
+    out = one.succeed(
+        "curl -f -d 'measurement,tag1=value1,tag2=value2 field1=123,field2=1.23' -X POST 'http://localhost:8428/write'"
+    )
+    cmd = (
+        """curl -f -s -G 'http://localhost:8428/api/v1/export' -d 'match={__name__!=""}'"""
+    )
+    # data takes a while to appear
+    one.wait_until_succeeds(f"[[ $({cmd} | wc -l) -ne 0 ]]")
+    out = one.succeed(cmd)
+    assert '"values":[123]' in out
+    assert '"values":[1.23]' in out
+  '';
+})
diff --git a/nixpkgs/nixos/tests/vikunja.nix b/nixpkgs/nixos/tests/vikunja.nix
new file mode 100644
index 000000000000..60fd5ce13854
--- /dev/null
+++ b/nixpkgs/nixos/tests/vikunja.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "vikunja";
+
+  meta.maintainers = with lib.maintainers; [ leona ];
+
+  nodes = {
+    vikunjaSqlite = { ... }: {
+      services.vikunja = {
+        enable = true;
+        database = {
+          type = "sqlite";
+        };
+        frontendScheme = "http";
+        frontendHostname = "localhost";
+      };
+      services.nginx.enable = true;
+    };
+    vikunjaPostgresql = { pkgs, ... }: {
+      services.vikunja = {
+        enable = true;
+        database = {
+          type = "postgres";
+          user = "vikunja-api";
+          database = "vikunja-api";
+          host = "/run/postgresql";
+        };
+        frontendScheme = "http";
+        frontendHostname = "localhost";
+        port = 9090;
+      };
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "vikunja-api" ];
+        ensureUsers = [
+          { name = "vikunja-api";
+            ensureDBOwnership = true;
+          }
+        ];
+      };
+      services.nginx.enable = true;
+    };
+  };
+
+  testScript =
+    ''
+      vikunjaSqlite.wait_for_unit("vikunja-api.service")
+      vikunjaSqlite.wait_for_open_port(3456)
+      vikunjaSqlite.succeed("curl --fail http://localhost:3456/api/v1/info")
+
+      vikunjaSqlite.wait_for_unit("nginx.service")
+      vikunjaSqlite.wait_for_open_port(80)
+      vikunjaSqlite.succeed("curl --fail http://localhost/api/v1/info")
+      vikunjaSqlite.succeed("curl --fail http://localhost")
+
+      vikunjaPostgresql.wait_for_unit("vikunja-api.service")
+      vikunjaPostgresql.wait_for_open_port(9090)
+      vikunjaPostgresql.succeed("curl --fail http://localhost:9090/api/v1/info")
+
+      vikunjaPostgresql.wait_for_unit("nginx.service")
+      vikunjaPostgresql.wait_for_open_port(80)
+      vikunjaPostgresql.succeed("curl --fail http://localhost/api/v1/info")
+      vikunjaPostgresql.succeed("curl --fail http://localhost")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/virtualbox.nix b/nixpkgs/nixos/tests/virtualbox.nix
new file mode 100644
index 000000000000..e522d0679e15
--- /dev/null
+++ b/nixpkgs/nixos/tests/virtualbox.nix
@@ -0,0 +1,522 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+  debug ? false,
+  enableUnfree ? false,
+  use64bitGuest ? true
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  testVMConfig = vmName: attrs: { config, pkgs, lib, ... }: let
+    guestAdditions = pkgs.linuxPackages.virtualboxGuestAdditions;
+
+    miniInit = ''
+      #!${pkgs.runtimeShell} -xe
+      export PATH="${lib.makeBinPath [ pkgs.coreutils pkgs.util-linux ]}"
+
+      mkdir -p /run/dbus /var
+      ln -s /run /var
+      cat > /etc/passwd <<EOF
+      root:x:0:0::/root:/bin/false
+      messagebus:x:1:1::/run/dbus:/bin/false
+      EOF
+      cat > /etc/group <<EOF
+      root:x:0:
+      messagebus:x:1:
+      EOF
+
+      "${pkgs.dbus}/bin/dbus-daemon" --fork \
+        --config-file="${pkgs.dbus}/share/dbus-1/system.conf"
+
+      ${guestAdditions}/bin/VBoxService
+      ${(attrs.vmScript or (const "")) pkgs}
+
+      i=0
+      while [ ! -e /mnt-root/shutdown ]; do
+        sleep 10
+        i=$(($i + 10))
+        [ $i -le 120 ] || fail
+      done
+
+      rm -f /mnt-root/boot-done /mnt-root/shutdown
+    '';
+  in {
+    boot.kernelParams = [
+      "console=tty0" "console=ttyS0" "ignore_loglevel"
+      "boot.trace" "panic=1" "boot.panic_on_fail"
+      "init=${pkgs.writeScript "mini-init.sh" miniInit}"
+    ];
+
+    fileSystems."/" = {
+      device = "vboxshare";
+      fsType = "vboxsf";
+    };
+
+    virtualisation.virtualbox.guest.enable = true;
+
+    boot.initrd.kernelModules = [
+      "af_packet" "vboxsf"
+      "virtio" "virtio_pci" "virtio_ring" "virtio_net" "vboxguest"
+    ];
+
+    boot.initrd.extraUtilsCommands = ''
+      copy_bin_and_libs "${guestAdditions}/bin/mount.vboxsf"
+      copy_bin_and_libs "${pkgs.util-linux}/bin/unshare"
+      ${(attrs.extraUtilsCommands or (const "")) pkgs}
+    '';
+
+    boot.initrd.postMountCommands = ''
+      touch /mnt-root/boot-done
+      hostname "${vmName}"
+      mkdir -p /nix/store
+      unshare -m ${escapeShellArg pkgs.runtimeShell} -c '
+        mount -t vboxsf nixstore /nix/store
+        exec "$stage2Init"
+      '
+      poweroff -f
+    '';
+
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isYes "SERIAL_8250_CONSOLE")
+      (isYes "SERIAL_8250")
+    ];
+
+    networking.usePredictableInterfaceNames = false;
+  };
+
+  mkLog = logfile: tag: let
+    rotated = map (i: "${logfile}.${toString i}") (range 1 9);
+    all = concatMapStringsSep " " (f: "\"${f}\"") ([logfile] ++ rotated);
+    logcmd = "tail -F ${all} 2> /dev/null | logger -t \"${tag}\"";
+  in if debug then "machine.execute(ru('${logcmd} & disown'))" else "pass";
+
+  testVM = vmName: vmScript: let
+    cfg = (import ../lib/eval-config.nix {
+      system = if use64bitGuest then "x86_64-linux" else "i686-linux";
+      modules = [
+        ../modules/profiles/minimal.nix
+        (testVMConfig vmName vmScript)
+      ];
+    }).config;
+  in pkgs.vmTools.runInLinuxVM (pkgs.runCommand "virtualbox-image" {
+    preVM = ''
+      mkdir -p "$out"
+      diskImage="$(pwd)/qimage"
+      ${pkgs.vmTools.qemu}/bin/qemu-img create -f raw "$diskImage" 100M
+    '';
+
+    postVM = ''
+      echo "creating VirtualBox disk image..."
+      ${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -O vdi \
+        "$diskImage" "$out/disk.vdi"
+    '';
+
+    buildInputs = [ pkgs.util-linux pkgs.perl ];
+  } ''
+    ${pkgs.parted}/sbin/parted --script /dev/vda mklabel msdos
+    ${pkgs.parted}/sbin/parted --script /dev/vda -- mkpart primary ext2 1M -1s
+    ${pkgs.e2fsprogs}/sbin/mkfs.ext4 /dev/vda1
+    ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1
+    mkdir /mnt
+    mount /dev/vda1 /mnt
+    cp "${cfg.system.build.kernel}/bzImage" /mnt/linux
+    cp "${cfg.system.build.initialRamdisk}/initrd" /mnt/initrd
+
+    ${pkgs.grub2}/bin/grub-install --boot-directory=/mnt /dev/vda
+
+    cat > /mnt/grub/grub.cfg <<GRUB
+    set root=hd0,1
+    linux /linux ${concatStringsSep " " cfg.boot.kernelParams}
+    initrd /initrd
+    boot
+    GRUB
+    umount /mnt
+  '');
+
+  createVM = name: attrs: let
+    mkFlags = concatStringsSep " ";
+
+    sharePath = "/home/alice/vboxshare-${name}";
+
+    createFlags = mkFlags [
+      "--ostype ${if use64bitGuest then "Linux26_64" else "Linux26"}"
+      "--register"
+    ];
+
+    vmFlags = mkFlags ([
+      "--uart1 0x3F8 4"
+      "--uartmode1 client /run/virtualbox-log-${name}.sock"
+      "--memory 768"
+      "--audio none"
+    ] ++ (attrs.vmFlags or []));
+
+    controllerFlags = mkFlags [
+      "--name SATA"
+      "--add sata"
+      "--bootable on"
+      "--hostiocache on"
+    ];
+
+    diskFlags = mkFlags [
+      "--storagectl SATA"
+      "--port 0"
+      "--device 0"
+      "--type hdd"
+      "--mtype immutable"
+      "--medium ${testVM name attrs}/disk.vdi"
+    ];
+
+    sharedFlags = mkFlags [
+      "--name vboxshare"
+      "--hostpath ${sharePath}"
+    ];
+
+    nixstoreFlags = mkFlags [
+      "--name nixstore"
+      "--hostpath /nix/store"
+      "--readonly"
+    ];
+  in {
+    machine = {
+      systemd.sockets."vboxtestlog-${name}" = {
+        description = "VirtualBox Test Machine Log Socket For ${name}";
+        wantedBy = [ "sockets.target" ];
+        before = [ "multi-user.target" ];
+        socketConfig.ListenStream = "/run/virtualbox-log-${name}.sock";
+        socketConfig.Accept = true;
+      };
+
+      systemd.services."vboxtestlog-${name}@" = {
+        description = "VirtualBox Test Machine Log For ${name}";
+        serviceConfig.StandardInput = "socket";
+        serviceConfig.StandardOutput = "journal";
+        serviceConfig.SyslogIdentifier = "GUEST-${name}";
+        serviceConfig.ExecStart = "${pkgs.coreutils}/bin/cat";
+      };
+    };
+
+    testSubs = ''
+
+
+      ${name}_sharepath = "${sharePath}"
+
+
+      def check_running_${name}():
+          cmd = "VBoxManage list runningvms | grep -q '^\"${name}\"'"
+          (status, _) = machine.execute(ru(cmd))
+          return status == 0
+
+
+      def cleanup_${name}():
+          if check_running_${name}():
+              machine.execute(ru("VBoxManage controlvm ${name} poweroff"))
+          machine.succeed("rm -rf ${sharePath}")
+          machine.succeed("mkdir -p ${sharePath}")
+          machine.succeed("chown alice:users ${sharePath}")
+
+
+      def create_vm_${name}():
+          cleanup_${name}()
+          vbm("createvm --name ${name} ${createFlags}")
+          vbm("modifyvm ${name} ${vmFlags}")
+          vbm("setextradata ${name} VBoxInternal/PDM/HaltOnReset 1")
+          vbm("storagectl ${name} ${controllerFlags}")
+          vbm("storageattach ${name} ${diskFlags}")
+          vbm("sharedfolder add ${name} ${sharedFlags}")
+          vbm("sharedfolder add ${name} ${nixstoreFlags}")
+
+          ${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"}
+
+
+      def destroy_vm_${name}():
+          cleanup_${name}()
+          vbm("unregistervm ${name} --delete")
+
+
+      def wait_for_vm_boot_${name}():
+          machine.execute(
+              ru(
+                  "set -e; i=0; "
+                  "while ! test -e ${sharePath}/boot-done; do "
+                  "sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; "
+                  "VBoxManage list runningvms | grep -q '^\"${name}\"'; "
+                  "done"
+              )
+          )
+
+
+      def wait_for_ip_${name}(interface):
+          property = f"/VirtualBox/GuestInfo/Net/{interface}/V4/IP"
+          getip = f"VBoxManage guestproperty get ${name} {property} | sed -n -e 's/^Value: //p'"
+
+          ip = machine.succeed(
+              ru(
+                  "for i in $(seq 1000); do "
+                  f'if ipaddr="$({getip})" && [ -n "$ipaddr" ]; then '
+                  'echo "$ipaddr"; exit 0; '
+                  "fi; "
+                  "sleep 1; "
+                  "done; "
+                  "echo 'Could not get IPv4 address for ${name}!' >&2; "
+                  "exit 1"
+              )
+          ).strip()
+          return ip
+
+
+      def wait_for_startup_${name}(nudge=lambda: None):
+          for _ in range(0, 130, 10):
+              machine.sleep(10)
+              if check_running_${name}():
+                  return
+              nudge()
+          raise Exception("VirtualBox VM didn't start up within 2 minutes")
+
+
+      def wait_for_shutdown_${name}():
+          for _ in range(0, 130, 10):
+              machine.sleep(10)
+              if not check_running_${name}():
+                  return
+          raise Exception("VirtualBox VM didn't shut down within 2 minutes")
+
+
+      def shutdown_vm_${name}():
+          machine.succeed(ru("touch ${sharePath}/shutdown"))
+          machine.execute(
+              "set -e; i=0; "
+              "while test -e ${sharePath}/shutdown "
+              "        -o -e ${sharePath}/boot-done; do "
+              "sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; "
+              "done"
+          )
+          wait_for_shutdown_${name}()
+    '';
+  };
+
+  hostonlyVMFlags = [
+    "--nictype1 virtio"
+    "--nictype2 virtio"
+    "--nic2 hostonly"
+    "--hostonlyadapter2 vboxnet0"
+  ];
+
+  # The VirtualBox Oracle Extension Pack lets you use USB 3.0 (xHCI).
+  enableExtensionPackVMFlags = [
+    "--usbxhci on"
+  ];
+
+  dhcpScript = pkgs: ''
+    ${pkgs.dhcpcd}/bin/dhcpcd eth0 eth1
+
+    otherIP="$(${pkgs.netcat}/bin/nc -l 1234 || :)"
+    ${pkgs.iputils}/bin/ping -I eth1 -c1 "$otherIP"
+    echo "$otherIP reachable" | ${pkgs.netcat}/bin/nc -l 5678 || :
+  '';
+
+  sysdDetectVirt = pkgs: ''
+    ${pkgs.systemd}/bin/systemd-detect-virt > /mnt-root/result
+  '';
+
+  vboxVMs = mapAttrs createVM {
+    simple = {};
+
+    detectvirt.vmScript = sysdDetectVirt;
+
+    test1.vmFlags = hostonlyVMFlags;
+    test1.vmScript = dhcpScript;
+
+    test2.vmFlags = hostonlyVMFlags;
+    test2.vmScript = dhcpScript;
+
+    headless.virtualisation.virtualbox.headless = true;
+    headless.services.xserver.enable = false;
+  };
+
+  vboxVMsWithExtpack = mapAttrs createVM {
+    testExtensionPack.vmFlags = enableExtensionPackVMFlags;
+  };
+
+  mkVBoxTest = useExtensionPack: vms: name: testScript: makeTest {
+    name = "virtualbox-${name}";
+
+    nodes.machine = { lib, config, ... }: {
+      imports = let
+        mkVMConf = name: val: val.machine // { key = "${name}-config"; };
+        vmConfigs = mapAttrsToList mkVMConf vms;
+      in [ ./common/user-account.nix ./common/x11.nix ] ++ vmConfigs;
+      virtualisation.memorySize = 2048;
+      virtualisation.qemu.options = ["-cpu" "kvm64,svm=on,vmx=on"];
+      virtualisation.virtualbox.host.enable = true;
+      test-support.displayManager.auto.user = "alice";
+      users.users.alice.extraGroups = let
+        inherit (config.virtualisation.virtualbox.host) enableHardening;
+      in lib.mkIf enableHardening (lib.singleton "vboxusers");
+      virtualisation.virtualbox.host.enableExtensionPack = useExtensionPack;
+      nixpkgs.config.allowUnfree = useExtensionPack;
+    };
+
+    testScript = ''
+      from shlex import quote
+      ${concatStrings (mapAttrsToList (_: getAttr "testSubs") vms)}
+
+      def ru(cmd: str) -> str:
+          return f"su - alice -c {quote(cmd)}"
+
+
+      def vbm(cmd: str) -> str:
+          return machine.succeed(ru(f"VBoxManage {cmd}"))
+
+
+      def remove_uuids(output: str) -> str:
+          return "\n".join(
+              [line for line in (output or "").splitlines() if not line.startswith("UUID:")]
+          )
+
+
+      machine.wait_for_x()
+
+      ${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"}
+
+      ${testScript}
+      # (keep black happy)
+    '';
+
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ aszlig ];
+    };
+  };
+
+  unfreeTests = mapAttrs (mkVBoxTest true vboxVMsWithExtpack) {
+    enable-extension-pack = ''
+      create_vm_testExtensionPack()
+      vbm("startvm testExtensionPack")
+      wait_for_startup_testExtensionPack()
+      machine.screenshot("cli_started")
+      wait_for_vm_boot_testExtensionPack()
+      machine.screenshot("cli_booted")
+
+      with machine.nested("Checking for privilege escalation"):
+          machine.fail("test -e '/root/VirtualBox VMs'")
+          machine.fail("test -e '/root/.config/VirtualBox'")
+          machine.succeed("test -e '/home/alice/VirtualBox VMs'")
+
+      shutdown_vm_testExtensionPack()
+      destroy_vm_testExtensionPack()
+    '';
+  };
+
+in mapAttrs (mkVBoxTest false vboxVMs) {
+  simple-gui = ''
+    # Home to select Tools, down to move to the VM, enter to start it.
+    def send_vm_startup():
+        machine.send_key("home")
+        machine.send_key("down")
+        machine.send_key("ret")
+
+
+    create_vm_simple()
+    machine.succeed(ru("VirtualBox >&2 &"))
+    machine.wait_until_succeeds(ru("xprop -name 'Oracle VM VirtualBox Manager'"))
+    machine.sleep(5)
+    machine.screenshot("gui_manager_started")
+    send_vm_startup()
+    machine.screenshot("gui_manager_sent_startup")
+    wait_for_startup_simple(send_vm_startup)
+    machine.screenshot("gui_started")
+    wait_for_vm_boot_simple()
+    machine.screenshot("gui_booted")
+    shutdown_vm_simple()
+    machine.sleep(5)
+    machine.screenshot("gui_stopped")
+    machine.send_key("ctrl-q")
+    machine.sleep(5)
+    machine.screenshot("gui_manager_stopped")
+    destroy_vm_simple()
+  '';
+
+  simple-cli = ''
+    create_vm_simple()
+    vbm("startvm simple")
+    wait_for_startup_simple()
+    machine.screenshot("cli_started")
+    wait_for_vm_boot_simple()
+    machine.screenshot("cli_booted")
+
+    with machine.nested("Checking for privilege escalation"):
+        machine.fail("test -e '/root/VirtualBox VMs'")
+        machine.fail("test -e '/root/.config/VirtualBox'")
+        machine.succeed("test -e '/home/alice/VirtualBox VMs'")
+
+    shutdown_vm_simple()
+    destroy_vm_simple()
+  '';
+
+  headless = ''
+    create_vm_headless()
+    machine.succeed(ru("VBoxHeadless --startvm headless >&2 & disown %1"))
+    wait_for_startup_headless()
+    wait_for_vm_boot_headless()
+    shutdown_vm_headless()
+    destroy_vm_headless()
+  '';
+
+  host-usb-permissions = ''
+    import sys
+
+    user_usb = remove_uuids(vbm("list usbhost"))
+    print(user_usb, file=sys.stderr)
+    root_usb = remove_uuids(machine.succeed("VBoxManage list usbhost"))
+    print(root_usb, file=sys.stderr)
+
+    if user_usb != root_usb:
+        raise Exception("USB host devices differ for root and normal user")
+    if "<none>" in user_usb:
+        raise Exception("No USB host devices found")
+  '';
+
+  systemd-detect-virt = ''
+    create_vm_detectvirt()
+    vbm("startvm detectvirt")
+    wait_for_startup_detectvirt()
+    wait_for_vm_boot_detectvirt()
+    shutdown_vm_detectvirt()
+    result = machine.succeed(f"cat '{detectvirt_sharepath}/result'").strip()
+    destroy_vm_detectvirt()
+    if result != "oracle":
+        raise Exception(f'systemd-detect-virt returned "{result}" instead of "oracle"')
+  '';
+
+  net-hostonlyif = ''
+    create_vm_test1()
+    create_vm_test2()
+
+    vbm("startvm test1")
+    wait_for_startup_test1()
+    wait_for_vm_boot_test1()
+
+    vbm("startvm test2")
+    wait_for_startup_test2()
+    wait_for_vm_boot_test2()
+
+    machine.screenshot("net_booted")
+
+    test1_ip = wait_for_ip_test1(1)
+    test2_ip = wait_for_ip_test2(1)
+
+    machine.succeed(f"echo '{test2_ip}' | nc -N '{test1_ip}' 1234")
+    machine.succeed(f"echo '{test1_ip}' | nc -N '{test2_ip}' 1234")
+
+    machine.wait_until_succeeds(f"nc -N '{test1_ip}' 5678 < /dev/null >&2")
+    machine.wait_until_succeeds(f"nc -N '{test2_ip}' 5678 < /dev/null >&2")
+
+    shutdown_vm_test1()
+    shutdown_vm_test2()
+
+    destroy_vm_test1()
+    destroy_vm_test2()
+  '';
+} // (optionalAttrs enableUnfree unfreeTests)
diff --git a/nixpkgs/nixos/tests/vscode-remote-ssh.nix b/nixpkgs/nixos/tests/vscode-remote-ssh.nix
new file mode 100644
index 000000000000..de7cc6badc9a
--- /dev/null
+++ b/nixpkgs/nixos/tests/vscode-remote-ssh.nix
@@ -0,0 +1,124 @@
+import ./make-test-python.nix ({ lib, ... }@args: let
+  pkgs = args.pkgs.extend (self: super: {
+    stdenv = super.stdenv.override {
+      config = super.config // {
+        allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
+          "vscode" "vscode-with-extensions" "vscode-extension-ms-vscode-remote-remote-ssh"
+        ];
+      };
+    };
+  });
+
+  inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+
+  inherit (pkgs.vscode.passthru) rev vscodeServer;
+in {
+  name = "vscode-remote-ssh";
+  meta.maintainers = with lib.maintainers; [ Enzime ];
+
+  nodes = let
+    serverAddress = "192.168.0.2";
+    clientAddress = "192.168.0.1";
+  in {
+    server = { ... }: {
+      networking.interfaces.eth1.ipv4.addresses = [ { address = serverAddress; prefixLength = 24; } ];
+      services.openssh.enable = true;
+      users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+      virtualisation.additionalPaths = with pkgs; [ patchelf bintools stdenv.cc.cc.lib ];
+    };
+    client = { ... }: {
+      imports = [ ./common/x11.nix ./common/user-account.nix ];
+      networking.interfaces.eth1.ipv4.addresses = [ { address = clientAddress; prefixLength = 24; } ];
+      networking.hosts.${serverAddress} = [ "server" ];
+      test-support.displayManager.auto.user = "alice";
+      environment.systemPackages = [
+        (pkgs.vscode-with-extensions.override {
+          vscodeExtensions = [
+            pkgs.vscode-extensions.ms-vscode-remote.remote-ssh
+          ];
+        })
+      ];
+    };
+  };
+
+  enableOCR = true;
+
+  testScript = let
+    jq = "${pkgs.jq}/bin/jq";
+
+    sshConfig = builtins.toFile "ssh.conf" ''
+      UserKnownHostsFile=/dev/null
+      StrictHostKeyChecking=no
+    '';
+
+    vscodeConfig = builtins.toFile "settings.json" ''
+      {
+        "window.zoomLevel": 1,
+        "security.workspace.trust.startupPrompt": "always"
+      }
+    '';
+  in ''
+    def connect_with_remote_ssh(screenshot, should_succeed):
+      print(f"connect_with_remote_ssh({screenshot=}, {should_succeed=})")
+
+      if server.execute("test -d ~/.vscode-server")[0] == 0:
+        server.succeed("rm -r ~/.vscode-server")
+
+      server.succeed("mkdir -p ~/.vscode-server/bin")
+      server.succeed("cp -r ${vscodeServer} ~/.vscode-server/bin/${rev}")
+
+      client.succeed("sudo -u alice code --remote=ssh-remote+root@server /root")
+      client.wait_for_window("Visual Studio Code")
+
+      client.wait_for_text("Do you trust the authors" if should_succeed else "Disconnected from SSH")
+      client.screenshot(screenshot)
+
+      if should_succeed:
+        # Press the Don't Trust button
+        client.send_key("tab")
+        client.send_key("tab")
+        client.send_key("tab")
+        client.send_key("\n")
+      else:
+        # Close the error dialog
+        client.send_key("esc")
+
+      # Don't send Ctrl-q too quickly otherwise it might not get sent to VS Code
+      client.sleep(1)
+      client.send_key("ctrl-q")
+      client.wait_until_fails("pidof code")
+
+
+    start_all()
+    server.wait_for_open_port(22)
+
+    VSCODE_COMMIT = server.execute("${jq} -r .commit ${pkgs.vscode}/lib/vscode/resources/app/product.json")[1].rstrip()
+    SERVER_COMMIT = server.execute("${jq} -r .commit ${vscodeServer}/product.json")[1].rstrip()
+
+    print(f"{VSCODE_COMMIT=} {SERVER_COMMIT=}")
+    assert VSCODE_COMMIT == SERVER_COMMIT, "VSCODE_COMMIT and SERVER_COMMIT do not match"
+
+    client.wait_until_succeeds("ping -c1 server")
+    client.succeed("sudo -u alice mkdir ~alice/.ssh")
+    client.succeed("sudo -u alice install -Dm 600 ${snakeOilPrivateKey} ~alice/.ssh/id_ecdsa")
+    client.succeed("sudo -u alice install ${sshConfig} ~alice/.ssh/config")
+    client.succeed("sudo -u alice install -Dm 644 ${vscodeConfig} ~alice/.config/Code/User/settings.json")
+
+    client.wait_for_x()
+    client.wait_for_file("~alice/.Xauthority")
+    client.succeed("xauth merge ~alice/.Xauthority")
+    # Move the mouse out of the way
+    client.succeed("${pkgs.xdotool}/bin/xdotool mousemove 0 0")
+
+    with subtest("fails to connect when nixpkgs isn't available"):
+      server.fail("nix-build '<nixpkgs>' -A hello")
+      connect_with_remote_ssh(screenshot="no_node_installed", should_succeed=False)
+      server.succeed("test -e ~/.vscode-server/bin/${rev}/node")
+      server.fail("~/.vscode-server/bin/${rev}/node -v")
+
+    with subtest("connects when server can patch Node"):
+      server.succeed("mkdir -p /nix/var/nix/profiles/per-user/root/channels")
+      server.succeed("ln -s ${pkgs.path} /nix/var/nix/profiles/per-user/root/channels/nixos")
+      connect_with_remote_ssh(screenshot="build_node_with_nix", should_succeed=True)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/vscodium.nix b/nixpkgs/nixos/tests/vscodium.nix
new file mode 100644
index 000000000000..d817ce927ff8
--- /dev/null
+++ b/nixpkgs/nixos/tests/vscodium.nix
@@ -0,0 +1,79 @@
+let
+  tests = {
+    wayland = { pkgs, ... }: {
+      imports = [ ./common/wayland-cage.nix ];
+
+      services.cage.program = "${pkgs.vscodium}/bin/codium";
+
+      environment.variables.NIXOS_OZONE_WL = "1";
+      environment.variables.DISPLAY = "do not use";
+
+      fonts.packages = with pkgs; [ dejavu_fonts ];
+    };
+    xorg = { pkgs, ... }: {
+      imports = [ ./common/user-account.nix ./common/x11.nix ];
+
+      virtualisation.memorySize = 2047;
+      services.xserver.enable = true;
+      services.xserver.displayManager.sessionCommands = ''
+        ${pkgs.vscodium}/bin/codium
+      '';
+      test-support.displayManager.auto.user = "alice";
+    };
+  };
+
+  mkTest = name: machine:
+    import ./make-test-python.nix ({ pkgs, ... }: {
+      inherit name;
+
+      nodes = { "${name}" = machine; };
+
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ synthetica turion ];
+      };
+      enableOCR = true;
+
+      testScript = ''
+        @polling_condition
+        def codium_running():
+            machine.succeed('pgrep -x codium')
+
+
+        start_all()
+
+        machine.wait_for_unit('graphical.target')
+
+        codium_running.wait() # type: ignore[union-attr]
+        with codium_running: # type: ignore[union-attr]
+            # Wait until vscodium is visible. "File" is in the menu bar.
+            machine.wait_for_text('Welcome')
+            machine.screenshot('start_screen')
+
+            test_string = 'testfile'
+
+            # Create a new file
+            machine.send_key('ctrl-n')
+            machine.wait_for_text('Untitled')
+            machine.screenshot('empty_editor')
+
+            # Type a string
+            machine.send_chars(test_string)
+            machine.wait_for_text(test_string)
+            machine.screenshot('editor')
+
+            # Save the file
+            machine.send_key('ctrl-s')
+            machine.wait_for_text('(Save|Desktop|alice|Size)')
+            machine.screenshot('save_window')
+            machine.send_key('ret')
+
+            # (the default filename is the first line of the file)
+            machine.wait_for_file(f'/home/alice/{test_string}')
+
+        # machine.send_key('ctrl-q')
+        # machine.wait_until_fails('pgrep -x codium')
+      '';
+    });
+
+in
+builtins.mapAttrs (k: v: mkTest k v { }) tests
diff --git a/nixpkgs/nixos/tests/vsftpd.nix b/nixpkgs/nixos/tests/vsftpd.nix
new file mode 100644
index 000000000000..6eaf32b22583
--- /dev/null
+++ b/nixpkgs/nixos/tests/vsftpd.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "vsftpd";
+
+  nodes = {
+    server = {
+      services.vsftpd = {
+        enable = true;
+        userlistDeny = false;
+        localUsers = true;
+        userlist = [ "ftp-test-user" ];
+        writeEnable = true;
+        localRoot = "/tmp";
+      };
+      networking.firewall.enable = false;
+
+      users = {
+        users.ftp-test-user = {
+          isSystemUser = true;
+          password = "ftp-test-password";
+          group = "ftp-test-group";
+        };
+        groups.ftp-test-group = {};
+      };
+    };
+
+    client = {};
+  };
+
+  testScript = ''
+    client.start()
+    server.wait_for_unit("vsftpd")
+    server.wait_for_open_port(21)
+
+    client.succeed("curl -u ftp-test-user:ftp-test-password ftp://server")
+    client.succeed('echo "this is a test" > /tmp/test.file.up')
+    client.succeed("curl -v -T /tmp/test.file.up -u ftp-test-user:ftp-test-password ftp://server")
+    client.succeed("curl -u ftp-test-user:ftp-test-password ftp://server/test.file.up > /tmp/test.file.down")
+    client.succeed("diff /tmp/test.file.up /tmp/test.file.down")
+    assert client.succeed("cat /tmp/test.file.up") == server.succeed("cat /tmp/test.file.up")
+    assert client.succeed("cat /tmp/test.file.down") == server.succeed("cat /tmp/test.file.up")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/warzone2100.nix b/nixpkgs/nixos/tests/warzone2100.nix
new file mode 100644
index 000000000000..568e04a46999
--- /dev/null
+++ b/nixpkgs/nixos/tests/warzone2100.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "warzone2100";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    services.xserver.enable = true;
+    environment.systemPackages = [ pkgs.warzone2100 ];
+  };
+
+  enableOCR = true;
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.execute("warzone2100 >&2 &")
+      machine.wait_for_window("Warzone 2100")
+      machine.wait_for_text(r"(Single Player|Multi Player|Tutorial|Options|Quit Game)")
+      machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/wasabibackend.nix b/nixpkgs/nixos/tests/wasabibackend.nix
new file mode 100644
index 000000000000..75730fe24d09
--- /dev/null
+++ b/nixpkgs/nixos/tests/wasabibackend.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "wasabibackend";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ mmahut ];
+  };
+
+  nodes = {
+    machine = { ... }: {
+      services.wasabibackend = {
+        enable = true;
+        network = "testnet";
+        rpc = {
+          user = "alice";
+          port = 18332;
+        };
+      };
+      services.bitcoind."testnet" = {
+        enable = true;
+        testnet = true;
+        rpc.users = {
+          alice.passwordHMAC = "e7096bc21da60b29ecdbfcdb2c3acc62$f948e61cb587c399358ed99c6ed245a41460b4bf75125d8330c9f6fcc13d7ae7";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("wasabibackend.service")
+    machine.wait_until_succeeds(
+        "grep 'Wasabi Backend started' /var/lib/wasabibackend/.walletwasabi/backend/Logs.txt"
+    )
+    machine.sleep(5)
+    machine.succeed(
+        "grep 'Config is successfully initialized' /var/lib/wasabibackend/.walletwasabi/backend/Logs.txt"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/watchdogd.nix b/nixpkgs/nixos/tests/watchdogd.nix
new file mode 100644
index 000000000000..663e97cbae10
--- /dev/null
+++ b/nixpkgs/nixos/tests/watchdogd.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "watchdogd";
+  meta.maintainers = with lib.maintainers; [ vifino ];
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.qemu.options = [
+      "-device i6300esb" # virtual watchdog timer
+    ];
+    boot.kernelModules = [ "i6300esb" ];
+    services.watchdogd.enable = true;
+    services.watchdogd.settings = {
+      supervisor.enabled = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("watchdogd.service")
+
+    assert "i6300ESB" in machine.succeed("watchdogctl status")
+    machine.succeed("watchdogctl test")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/gotosocial.nix b/nixpkgs/nixos/tests/web-apps/gotosocial.nix
new file mode 100644
index 000000000000..6d279ab63a79
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/gotosocial.nix
@@ -0,0 +1,28 @@
+{ lib, ... }:
+{
+  name = "gotosocial";
+  meta.maintainers = with lib.maintainers; [ misuzu ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.jq ];
+    services.gotosocial = {
+      enable = true;
+      setupPostgresqlDB = true;
+      settings = {
+        host = "localhost:8081";
+        port = 8081;
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("gotosocial.service")
+    machine.wait_for_unit("postgresql.service")
+    machine.wait_for_open_port(8081)
+
+    # check user registration via cli
+    machine.succeed("curl -sS -f http://localhost:8081/nodeinfo/2.0 | jq '.usage.users.total' | grep -q '^0$'")
+    machine.succeed("gotosocial-admin account create --username nickname --email email@example.com --password kurtz575VPeBgjVm")
+    machine.succeed("curl -sS -f http://localhost:8081/nodeinfo/2.0 | jq '.usage.users.total' | grep -q '^1$'")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/web-apps/healthchecks.nix b/nixpkgs/nixos/tests/web-apps/healthchecks.nix
new file mode 100644
index 000000000000..41c40cd5dd8d
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/healthchecks.nix
@@ -0,0 +1,42 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "healthchecks";
+
+  meta = with lib.maintainers; {
+    maintainers = [ phaer ];
+  };
+
+  nodes.machine = { ... }: {
+    services.healthchecks = {
+      enable = true;
+      settings = {
+        SITE_NAME = "MyUniqueInstance";
+        COMPRESS_ENABLED = "True";
+        SECRET_KEY_FILE = pkgs.writeText "secret"
+          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("healthchecks.target")
+    machine.wait_until_succeeds("journalctl --since -1m --unit healthchecks --grep Listening")
+
+    with subtest("Home screen loads"):
+        machine.succeed(
+            "curl -sSfL http://localhost:8000 | grep '<title>Sign In'"
+        )
+
+    with subtest("Setting SITE_NAME via freeform option works"):
+        machine.succeed(
+            "curl -sSfL http://localhost:8000 | grep 'MyUniqueInstance</title>'"
+        )
+
+    with subtest("Manage script works"):
+        # "shell" sucommand should succeed, needs python in PATH.
+        assert "foo\n" == machine.succeed("echo 'print(\"foo\")' | sudo -u healthchecks healthchecks-manage shell")
+
+        # Shouldn't fail if not called by healthchecks user
+        assert "foo\n" == machine.succeed("echo 'print(\"foo\")' | healthchecks-manage shell")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon/default.nix b/nixpkgs/nixos/tests/web-apps/mastodon/default.nix
new file mode 100644
index 000000000000..178590d13b63
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/mastodon/default.nix
@@ -0,0 +1,9 @@
+{ system ? builtins.currentSystem, handleTestOn }:
+let
+  supportedSystems = [ "x86_64-linux" "i686-linux" "aarch64-linux" ];
+
+in
+{
+  standard = handleTestOn supportedSystems ./standard.nix { inherit system; };
+  remote-databases = handleTestOn supportedSystems ./remote-databases.nix { inherit system; };
+}
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon/remote-databases.nix b/nixpkgs/nixos/tests/web-apps/mastodon/remote-databases.nix
new file mode 100644
index 000000000000..fa6430a99353
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/mastodon/remote-databases.nix
@@ -0,0 +1,190 @@
+import ../../make-test-python.nix ({pkgs, ...}:
+let
+  cert = pkgs: pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=mastodon.local' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+
+  hosts = ''
+    192.168.2.103 mastodon.local
+  '';
+
+in
+{
+  name = "mastodon-remote-postgresql";
+  meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin ];
+
+  nodes = {
+    databases = { config, ... }: {
+      environment = {
+        etc = {
+          "redis/password-redis-db".text = ''
+            ogjhJL8ynrP7MazjYOF6
+          '';
+        };
+      };
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.102"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [
+          config.services.redis.servers.mastodon.port
+          config.services.postgresql.port
+        ];
+      };
+
+      services.redis.servers.mastodon = {
+        enable = true;
+        bind = "0.0.0.0";
+        port = 31637;
+        requirePassFile = "/etc/redis/password-redis-db";
+      };
+
+      services.postgresql = {
+        enable = true;
+        # TODO remove once https://github.com/NixOS/nixpkgs/pull/266270 is resolved.
+        package = pkgs.postgresql_14;
+        enableTCPIP = true;
+        authentication = ''
+          hostnossl mastodon_local mastodon_test 192.168.2.201/32 md5
+        '';
+        initialScript = pkgs.writeText "postgresql_init.sql" ''
+          CREATE ROLE mastodon_test LOGIN PASSWORD 'SoDTZcISc3f1M1LJsRLT';
+          CREATE DATABASE mastodon_local TEMPLATE template0 ENCODING UTF8;
+          GRANT ALL PRIVILEGES ON DATABASE mastodon_local TO mastodon_test;
+        '';
+      };
+    };
+
+    nginx = { nodes, ... }: {
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.103"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [ 80 443 ];
+      };
+
+      security = {
+        pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      };
+
+      services.nginx = {
+        enable = true;
+        recommendedProxySettings = true;
+        virtualHosts."mastodon.local" = {
+          root = "/var/empty";
+          forceSSL = true;
+          enableACME = pkgs.lib.mkForce false;
+          sslCertificate = "${cert pkgs}/cert.pem";
+          sslCertificateKey = "${cert pkgs}/key.pem";
+          locations."/" = {
+            tryFiles = "$uri @proxy";
+          };
+          locations."@proxy" = {
+            proxyPass = "http://192.168.2.201:${toString nodes.server.services.mastodon.webPort}";
+            proxyWebsockets = true;
+          };
+        };
+      };
+    };
+
+    server = { config, pkgs, ... }: {
+      virtualisation.memorySize = 2048;
+
+      environment = {
+        etc = {
+          "mastodon/password-redis-db".text = ''
+            ogjhJL8ynrP7MazjYOF6
+          '';
+          "mastodon/password-posgressql-db".text = ''
+            SoDTZcISc3f1M1LJsRLT
+          '';
+        };
+      };
+
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.201"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [
+          config.services.mastodon.webPort
+          config.services.mastodon.sidekiqPort
+        ];
+      };
+
+      services.mastodon = {
+        enable = true;
+        configureNginx = false;
+        localDomain = "mastodon.local";
+        enableUnixSocket = false;
+        streamingProcesses = 2;
+        redis = {
+          createLocally = false;
+          host = "192.168.2.102";
+          port = 31637;
+          passwordFile = "/etc/mastodon/password-redis-db";
+        };
+        database = {
+          createLocally = false;
+          host = "192.168.2.102";
+          port = 5432;
+          name = "mastodon_local";
+          user = "mastodon_test";
+          passwordFile = "/etc/mastodon/password-posgressql-db";
+        };
+        smtp = {
+          createLocally = false;
+          fromAddress = "mastodon@mastodon.local";
+        };
+        extraConfig = {
+          BIND = "0.0.0.0";
+          EMAIL_DOMAIN_ALLOWLIST = "example.com";
+          RAILS_SERVE_STATIC_FILES = "true";
+          TRUSTED_PROXY_IP = "192.168.2.103";
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.202"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+      };
+
+      security = {
+        pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      };
+    };
+  };
+
+  testScript = import ./script.nix {
+    inherit pkgs;
+    extraInit = ''
+      nginx.wait_for_unit("nginx.service")
+      nginx.wait_for_open_port(443)
+      databases.wait_for_unit("redis-mastodon.service")
+      databases.wait_for_unit("postgresql.service")
+      databases.wait_for_open_port(31637)
+      databases.wait_for_open_port(5432)
+    '';
+    extraShutdown = ''
+      nginx.shutdown()
+      databases.shutdown()
+    '';
+  };
+})
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon/script.nix b/nixpkgs/nixos/tests/web-apps/mastodon/script.nix
new file mode 100644
index 000000000000..9184c63c8941
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/mastodon/script.nix
@@ -0,0 +1,52 @@
+{ pkgs
+, extraInit ? ""
+, extraShutdown ? ""
+}:
+
+''
+  start_all()
+
+  ${extraInit}
+
+  server.wait_for_unit("mastodon-sidekiq-all.service")
+  server.wait_for_unit("mastodon-streaming.target")
+  server.wait_for_unit("mastodon-web.service")
+  server.wait_for_open_port(55001)
+
+  # Check that mastodon-media-auto-remove is scheduled
+  server.succeed("systemctl status mastodon-media-auto-remove.timer")
+
+  # Check Mastodon version from remote client
+  client.succeed("curl --fail https://mastodon.local/api/v1/instance | jq -r '.version' | grep '${pkgs.mastodon.version}'")
+
+  # Check access from remote client
+  client.succeed("curl --fail https://mastodon.local/about | grep 'Mastodon hosted on mastodon.local'")
+  client.succeed("curl --fail $(curl https://mastodon.local/api/v1/instance 2> /dev/null | jq -r .thumbnail) --output /dev/null")
+
+  # Simple check tootctl commands
+  # Check Mastodon version
+  server.succeed("mastodon-tootctl version | grep '${pkgs.mastodon.version}'")
+
+  # Manage accounts
+  server.succeed("mastodon-tootctl email_domain_blocks add example.com")
+  server.succeed("mastodon-tootctl email_domain_blocks list | grep example.com")
+  server.fail("mastodon-tootctl email_domain_blocks list | grep mastodon.local")
+  server.fail("mastodon-tootctl accounts create alice --email=alice@example.com")
+  server.succeed("mastodon-tootctl email_domain_blocks remove example.com")
+  server.succeed("mastodon-tootctl accounts create bob --email=bob@example.com")
+  server.succeed("mastodon-tootctl accounts approve bob")
+  server.succeed("mastodon-tootctl accounts delete bob")
+
+  # Manage IP access
+  server.succeed("mastodon-tootctl ip_blocks add 192.168.0.0/16 --severity=no_access")
+  server.succeed("mastodon-tootctl ip_blocks export | grep 192.168.0.0/16")
+  server.fail("mastodon-tootctl ip_blocks export | grep 172.16.0.0/16")
+  client.fail("curl --fail https://mastodon.local/about")
+  server.succeed("mastodon-tootctl ip_blocks remove 192.168.0.0/16")
+  client.succeed("curl --fail https://mastodon.local/about")
+
+  server.shutdown()
+  client.shutdown()
+
+  ${extraShutdown}
+''
diff --git a/nixpkgs/nixos/tests/web-apps/mastodon/standard.nix b/nixpkgs/nixos/tests/web-apps/mastodon/standard.nix
new file mode 100644
index 000000000000..ddc764e2168c
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/mastodon/standard.nix
@@ -0,0 +1,91 @@
+import ../../make-test-python.nix ({pkgs, ...}:
+let
+  cert = pkgs: pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=mastodon.local' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+
+  hosts = ''
+    192.168.2.101 mastodon.local
+  '';
+
+in
+{
+  name = "mastodon-standard";
+  meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin turion ];
+
+  nodes = {
+    server = { pkgs, ... }: {
+
+      virtualisation.memorySize = 2048;
+
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.101"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+        firewall.allowedTCPPorts = [ 80 443 ];
+      };
+
+      security = {
+        pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      };
+
+      # TODO remove once https://github.com/NixOS/nixpkgs/pull/266270 is resolved.
+      services.postgresql.package = pkgs.postgresql_14;
+
+      services.mastodon = {
+        enable = true;
+        configureNginx = true;
+        localDomain = "mastodon.local";
+        enableUnixSocket = false;
+        streamingProcesses = 2;
+        smtp = {
+          createLocally = false;
+          fromAddress = "mastodon@mastodon.local";
+        };
+        extraConfig = {
+          EMAIL_DOMAIN_ALLOWLIST = "example.com";
+        };
+      };
+
+      services.nginx = {
+        virtualHosts."mastodon.local" = {
+          enableACME = pkgs.lib.mkForce false;
+          sslCertificate = "${cert pkgs}/cert.pem";
+          sslCertificateKey = "${cert pkgs}/key.pem";
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.102"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+      };
+
+      security = {
+        pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      };
+    };
+  };
+
+  testScript = import ./script.nix {
+    inherit pkgs;
+    extraInit = ''
+      server.wait_for_unit("nginx.service")
+      server.wait_for_open_port(443)
+      server.wait_for_unit("redis-mastodon.service")
+      server.wait_for_unit("postgresql.service")
+      server.wait_for_open_port(5432)
+    '';
+  };
+})
diff --git a/nixpkgs/nixos/tests/web-apps/monica.nix b/nixpkgs/nixos/tests/web-apps/monica.nix
new file mode 100644
index 000000000000..29f5cb85bb13
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/monica.nix
@@ -0,0 +1,33 @@
+import ../make-test-python.nix ({pkgs, ...}:
+let
+  cert = pkgs.runCommand "selfSignedCerts" { nativeBuildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=localhost' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+in
+{
+  name = "monica";
+
+  nodes = {
+    machine = {pkgs, ...}: {
+      services.monica = {
+        enable = true;
+        hostname = "localhost";
+        appKeyFile = "${pkgs.writeText "keyfile" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}";
+        nginx = {
+          forceSSL = true;
+          sslCertificate = "${cert}/cert.pem";
+          sslCertificateKey = "${cert}/key.pem";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("monica-setup.service")
+    machine.wait_for_open_port(443)
+    machine.succeed("curl -k --fail https://localhost", timeout=10)
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/netbox-upgrade.nix b/nixpkgs/nixos/tests/web-apps/netbox-upgrade.nix
new file mode 100644
index 000000000000..4c554e7ae613
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/netbox-upgrade.nix
@@ -0,0 +1,87 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: let
+  oldNetbox = pkgs.netbox_3_6;
+  newNetbox = pkgs.netbox_3_7;
+in {
+  name = "netbox-upgrade";
+
+  meta = with lib.maintainers; {
+    maintainers = [ minijackson raitobezarius ];
+  };
+
+  nodes.machine = { config, ... }: {
+    virtualisation.memorySize = 2048;
+    services.netbox = {
+      enable = true;
+      package = oldNetbox;
+      secretKeyFile = pkgs.writeText "secret" ''
+        abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+      '';
+    };
+
+    services.nginx = {
+      enable = true;
+
+      recommendedProxySettings = true;
+
+      virtualHosts.netbox = {
+        default = true;
+        locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
+        locations."/static/".alias = "/var/lib/netbox/static/";
+      };
+    };
+
+    users.users.nginx.extraGroups = [ "netbox" ];
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
+
+    specialisation.upgrade.configuration.services.netbox.package = lib.mkForce newNetbox;
+  };
+
+  testScript = { nodes, ... }:
+    let
+      apiVersion = version: lib.pipe version [
+        (lib.splitString ".")
+        (lib.take 2)
+        (lib.concatStringsSep ".")
+      ];
+      oldApiVersion = apiVersion oldNetbox.version;
+      newApiVersion = apiVersion newNetbox.version;
+    in
+    ''
+      start_all()
+      machine.wait_for_unit("netbox.target")
+      machine.wait_for_unit("nginx.service")
+      machine.wait_until_succeeds("journalctl --since -1m --unit netbox --grep Listening")
+
+      def api_version(headers):
+          header = [header for header in headers.splitlines() if header.startswith("API-Version:")][0]
+          return header.split()[1]
+
+      def check_api_version(version):
+          headers = machine.succeed(
+            "curl -sSfL http://localhost/api/ --head -H 'Content-Type: application/json'"
+          )
+          assert api_version(headers) == version
+
+      with subtest("NetBox version is the old one"):
+          check_api_version("${oldApiVersion}")
+
+      # Somehow, even though netbox-housekeeping.service has After=netbox.service,
+      # netbox-housekeeping.service and netbox.service still get started at the
+      # same time, making netbox-housekeeping fail (can't really do some house
+      # keeping job if the database is not correctly formed).
+      #
+      # So we don't check that the upgrade went well, we just check that
+      # netbox.service is active, and that netbox-housekeeping can be run
+      # successfully afterwards.
+      #
+      # This is not good UX, but the system should be working nonetheless.
+      machine.execute("${nodes.machine.system.build.toplevel}/specialisation/upgrade/bin/switch-to-configuration test >&2")
+
+      machine.wait_for_unit("netbox.service")
+      machine.succeed("systemctl start netbox-housekeeping.service")
+
+      with subtest("NetBox version is the new one"):
+          check_api_version("${newApiVersion}")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/netbox.nix b/nixpkgs/nixos/tests/web-apps/netbox.nix
new file mode 100644
index 000000000000..233f16a8fe0d
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/netbox.nix
@@ -0,0 +1,318 @@
+let
+  ldapDomain = "example.org";
+  ldapSuffix = "dc=example,dc=org";
+
+  ldapRootUser = "admin";
+  ldapRootPassword = "foobar";
+
+  testUser = "alice";
+  testPassword = "verySecure";
+  testGroup = "netbox-users";
+in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: {
+  name = "netbox";
+
+  meta = with lib.maintainers; {
+    maintainers = [ minijackson n0emis ];
+  };
+
+  nodes.machine = { config, ... }: {
+    virtualisation.memorySize = 2048;
+    services.netbox = {
+      enable = true;
+      package = netbox;
+      secretKeyFile = pkgs.writeText "secret" ''
+        abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+      '';
+
+      enableLdap = true;
+      ldapConfigPath = pkgs.writeText "ldap_config.py" ''
+        import ldap
+        from django_auth_ldap.config import LDAPSearch, PosixGroupType
+
+        AUTH_LDAP_SERVER_URI = "ldap://localhost/"
+
+        AUTH_LDAP_USER_SEARCH = LDAPSearch(
+            "ou=accounts,ou=posix,${ldapSuffix}",
+            ldap.SCOPE_SUBTREE,
+            "(uid=%(user)s)",
+        )
+
+        AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
+            "ou=groups,ou=posix,${ldapSuffix}",
+            ldap.SCOPE_SUBTREE,
+            "(objectClass=posixGroup)",
+        )
+        AUTH_LDAP_GROUP_TYPE = PosixGroupType()
+
+        # Mirror LDAP group assignments.
+        AUTH_LDAP_MIRROR_GROUPS = True
+
+        # For more granular permissions, we can map LDAP groups to Django groups.
+        AUTH_LDAP_FIND_GROUP_PERMS = True
+      '';
+    };
+
+    services.nginx = {
+      enable = true;
+
+      recommendedProxySettings = true;
+
+      virtualHosts.netbox = {
+        default = true;
+        locations."/".proxyPass = "http://localhost:${toString config.services.netbox.port}";
+        locations."/static/".alias = "/var/lib/netbox/static/";
+      };
+    };
+
+    # Adapted from the sssd-ldap NixOS test
+    services.openldap = {
+      enable = true;
+      settings = {
+        children = {
+          "cn=schema".includes = [
+            "${pkgs.openldap}/etc/schema/core.ldif"
+            "${pkgs.openldap}/etc/schema/cosine.ldif"
+            "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+            "${pkgs.openldap}/etc/schema/nis.ldif"
+          ];
+          "olcDatabase={1}mdb" = {
+            attrs = {
+              objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+              olcDatabase = "{1}mdb";
+              olcDbDirectory = "/var/lib/openldap/db";
+              olcSuffix = ldapSuffix;
+              olcRootDN = "cn=${ldapRootUser},${ldapSuffix}";
+              olcRootPW = ldapRootPassword;
+            };
+          };
+        };
+      };
+      declarativeContents = {
+        ${ldapSuffix} = ''
+          dn: ${ldapSuffix}
+          objectClass: top
+          objectClass: dcObject
+          objectClass: organization
+          o: ${ldapDomain}
+
+          dn: ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: ou=accounts,ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: uid=${testUser},ou=accounts,ou=posix,${ldapSuffix}
+          objectClass: person
+          objectClass: posixAccount
+          userPassword: ${testPassword}
+          homeDirectory: /home/${testUser}
+          uidNumber: 1234
+          gidNumber: 1234
+          cn: ""
+          sn: ""
+
+          dn: ou=groups,ou=posix,${ldapSuffix}
+          objectClass: top
+          objectClass: organizationalUnit
+
+          dn: cn=${testGroup},ou=groups,ou=posix,${ldapSuffix}
+          objectClass: posixGroup
+          gidNumber: 2345
+          memberUid: ${testUser}
+        '';
+      };
+    };
+
+    users.users.nginx.extraGroups = [ "netbox" ];
+
+    networking.firewall.allowedTCPPorts = [ 80 ];
+  };
+
+  testScript = let
+    changePassword = pkgs.writeText "change-password.py" ''
+      from django.contrib.auth.models import User
+      u = User.objects.get(username='netbox')
+      u.set_password('netbox')
+      u.save()
+    '';
+  in ''
+    from typing import Any, Dict
+    import json
+
+    start_all()
+    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")
+
+    with subtest("Superuser can be created"):
+        machine.succeed(
+            "netbox-manage createsuperuser --noinput --username netbox --email netbox@example.com"
+        )
+        # Django doesn't have a "clean" way of inputting the password from the command line
+        machine.succeed("cat '${changePassword}' | netbox-manage shell")
+
+    machine.wait_for_unit("network.target")
+
+    with subtest("Home screen loads from nginx"):
+        machine.succeed(
+            "curl -sSfL http://localhost | grep '<title>Home | NetBox</title>'"
+        )
+
+    with subtest("Staticfiles can be fetched"):
+        machine.succeed("curl -sSfL http://localhost/static/netbox.js")
+        machine.succeed("curl -sSfL http://localhost/static/docs/")
+
+    with subtest("Can interact with API"):
+        json.loads(
+            machine.succeed("curl -sSfL -H 'Accept: application/json' 'http://localhost/api/'")
+        )
+
+    def login(username: str, password: str):
+        encoded_data = json.dumps({"username": username, "password": password})
+        uri = "/users/tokens/provision/"
+        result = json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-X POST "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"'http://localhost/api{uri}' "
+                f"--data '{encoded_data}'"
+            )
+        )
+        return result["key"]
+
+    with subtest("Can login"):
+        auth_token = login("netbox", "netbox")
+
+    def get(uri: str):
+        return json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-H 'Accept: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                f"'http://localhost/api{uri}'"
+            )
+        )
+
+    def delete(uri: str):
+        return machine.succeed(
+            "curl -sSfL "
+            f"-X DELETE "
+            "-H 'Accept: application/json' "
+            f"-H 'Authorization: Token {auth_token}' "
+            f"'http://localhost/api{uri}'"
+        )
+
+
+    def data_request(uri: str, method: str, data: Dict[str, Any]):
+        encoded_data = json.dumps(data)
+        return json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                f"-X {method} "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                f"'http://localhost/api{uri}' "
+                f"--data '{encoded_data}'"
+            )
+        )
+
+    def post(uri: str, data: Dict[str, Any]):
+      return data_request(uri, "POST", data)
+
+    def patch(uri: str, data: Dict[str, Any]):
+      return data_request(uri, "PATCH", data)
+
+    with subtest("Can create objects"):
+        result = post("/dcim/sites/", {"name": "Test site", "slug": "test-site"})
+        site_id = result["id"]
+
+        # Example from:
+        # http://netbox.extra.cea.fr/static/docs/integrations/rest-api/#creating-a-new-object
+        post("/ipam/prefixes/", {"prefix": "192.0.2.0/24", "site": site_id})
+
+        result = post(
+            "/dcim/manufacturers/",
+            {"name": "Test manufacturer", "slug": "test-manufacturer"}
+        )
+        manufacturer_id = result["id"]
+
+        # Had an issue with device-types before NetBox 3.4.0
+        result = post(
+            "/dcim/device-types/",
+            {
+                "model": "Test device type",
+                "manufacturer": manufacturer_id,
+                "slug": "test-device-type",
+            },
+        )
+        device_type_id = result["id"]
+
+    with subtest("Can list objects"):
+        result = get("/dcim/sites/")
+
+        assert result["count"] == 1
+        assert result["results"][0]["id"] == site_id
+        assert result["results"][0]["name"] == "Test site"
+        assert result["results"][0]["description"] == ""
+
+        result = get("/dcim/device-types/")
+        assert result["count"] == 1
+        assert result["results"][0]["id"] == device_type_id
+        assert result["results"][0]["model"] == "Test device type"
+
+    with subtest("Can update objects"):
+        new_description = "Test site description"
+        patch(f"/dcim/sites/{site_id}/", {"description": new_description})
+        result = get(f"/dcim/sites/{site_id}/")
+        assert result["description"] == new_description
+
+    with subtest("Can delete objects"):
+        # Delete a device-type since no object depends on it
+        delete(f"/dcim/device-types/{device_type_id}/")
+
+        result = get("/dcim/device-types/")
+        assert result["count"] == 0
+
+    with subtest("Can use the GraphQL API"):
+        encoded_data = json.dumps({
+            "query": "query { prefix_list { prefix, site { id, description } } }",
+        })
+        result = json.loads(
+            machine.succeed(
+                "curl -sSfL "
+                "-H 'Accept: application/json' "
+                "-H 'Content-Type: application/json' "
+                f"-H 'Authorization: Token {auth_token}' "
+                "'http://localhost/graphql/' "
+                f"--data '{encoded_data}'"
+            )
+        )
+
+        assert len(result["data"]["prefix_list"]) == 1
+        assert result["data"]["prefix_list"][0]["prefix"] == "192.0.2.0/24"
+        assert result["data"]["prefix_list"][0]["site"]["id"] == str(site_id)
+        assert result["data"]["prefix_list"][0]["site"]["description"] == new_description
+
+    with subtest("Can login with LDAP"):
+        machine.wait_for_unit("openldap.service")
+        login("alice", "${testPassword}")
+
+    with subtest("Can associate LDAP groups"):
+        result = get("/users/users/?username=${testUser}")
+
+        assert result["count"] == 1
+        assert any(group["name"] == "${testGroup}" for group in result["results"][0]["groups"])
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/nifi.nix b/nixpkgs/nixos/tests/web-apps/nifi.nix
new file mode 100644
index 000000000000..92f7fa231df3
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/nifi.nix
@@ -0,0 +1,30 @@
+import ../make-test-python.nix ({pkgs, ...}:
+{
+  name = "nifi";
+  meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
+
+  nodes = {
+    nifi = { pkgs, ... }: {
+      virtualisation = {
+        memorySize = 2048;
+        diskSize = 4096;
+      };
+      services.nifi = {
+        enable = true;
+        enableHTTPS = false;
+      };
+    };
+  };
+
+  testScript = ''
+    nifi.start()
+
+    nifi.wait_for_unit("nifi.service")
+    nifi.wait_for_open_port(8080)
+
+    # Check if NiFi is running
+    nifi.succeed("curl --fail http://127.0.0.1:8080/nifi/login 2> /dev/null | grep 'NiFi Login'")
+
+    nifi.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/peering-manager.nix b/nixpkgs/nixos/tests/web-apps/peering-manager.nix
new file mode 100644
index 000000000000..3f0acd560d13
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/peering-manager.nix
@@ -0,0 +1,40 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "peering-manager";
+
+  meta = with lib.maintainers; {
+    maintainers = [ yuka ];
+  };
+
+  nodes.machine = { ... }: {
+    services.peering-manager = {
+      enable = true;
+      secretKeyFile = pkgs.writeText "secret" ''
+        abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
+      '';
+    };
+  };
+
+  testScript = { nodes }: ''
+    machine.start()
+    machine.wait_for_unit("peering-manager.target")
+    machine.wait_until_succeeds("journalctl --since -1m --unit peering-manager --grep Listening")
+
+    print(machine.succeed(
+        "curl -sSfL http://[::1]:8001"
+    ))
+    with subtest("Home screen loads"):
+        machine.succeed(
+            "curl -sSfL http://[::1]:8001 | grep '<title>Home - Peering Manager</title>'"
+        )
+    with subtest("checks succeed"):
+        machine.succeed(
+            "systemctl stop peering-manager peering-manager-rq"
+        )
+        machine.succeed(
+            "sudo -u postgres psql -c 'ALTER USER \"peering-manager\" WITH SUPERUSER;'"
+        )
+        machine.succeed(
+            "cd ${nodes.machine.system.build.peeringManagerPkg}/opt/peering-manager ; peering-manager-manage test --no-input"
+        )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/peertube.nix b/nixpkgs/nixos/tests/web-apps/peertube.nix
new file mode 100644
index 000000000000..0e5f39c08a02
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/peertube.nix
@@ -0,0 +1,139 @@
+import ../make-test-python.nix ({pkgs, ...}:
+{
+  name = "peertube";
+  meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
+
+  nodes = {
+    database = {
+      networking = {
+       interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.10"; prefixLength = 24; }
+          ];
+        };
+        firewall.allowedTCPPorts = [ 5432 31638 ];
+      };
+
+      services.postgresql = {
+        enable = true;
+        enableTCPIP = true;
+        authentication = ''
+          hostnossl peertube_local peertube_test 192.168.2.11/32 md5
+        '';
+        initialScript = pkgs.writeText "postgresql_init.sql" ''
+          CREATE ROLE peertube_test LOGIN PASSWORD '0gUN0C1mgST6czvjZ8T9';
+          CREATE DATABASE peertube_local TEMPLATE template0 ENCODING UTF8;
+          GRANT ALL PRIVILEGES ON DATABASE peertube_local TO peertube_test;
+          \connect peertube_local
+          CREATE EXTENSION IF NOT EXISTS pg_trgm;
+          CREATE EXTENSION IF NOT EXISTS unaccent;
+        '';
+      };
+
+      services.redis.servers.peertube = {
+        enable = true;
+        bind = "0.0.0.0";
+        requirePass = "turrQfaQwnanGbcsdhxy";
+        port = 31638;
+      };
+    };
+
+    server = { pkgs, ... }: {
+      environment = {
+        etc = {
+          "peertube/secrets-peertube".text = ''
+            063d9c60d519597acef26003d5ecc32729083965d09181ef3949200cbe5f09ee
+          '';
+          "peertube/password-posgressql-db".text = ''
+            0gUN0C1mgST6czvjZ8T9
+          '';
+          "peertube/password-redis-db".text = ''
+            turrQfaQwnanGbcsdhxy
+          '';
+        };
+      };
+
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.11"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = ''
+          192.168.2.11 peertube.local
+        '';
+        firewall.allowedTCPPorts = [ 9000 ];
+      };
+
+      services.peertube = {
+        enable = true;
+        localDomain = "peertube.local";
+        enableWebHttps = false;
+
+        secrets = {
+          secretsFile = "/etc/peertube/secrets-peertube";
+        };
+
+        database = {
+          host = "192.168.2.10";
+          name = "peertube_local";
+          user = "peertube_test";
+          passwordFile = "/etc/peertube/password-posgressql-db";
+        };
+
+        redis = {
+          host = "192.168.2.10";
+          port = 31638;
+          passwordFile = "/etc/peertube/password-redis-db";
+        };
+
+        settings = {
+          listen = {
+            hostname = "0.0.0.0";
+          };
+          instance = {
+            name = "PeerTube Test Server";
+          };
+        };
+      };
+    };
+
+    client = {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+       interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.12"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = ''
+          192.168.2.11 peertube.local
+        '';
+      };
+    };
+
+  };
+
+  testScript = ''
+    start_all()
+
+    database.wait_for_unit("postgresql.service")
+    database.wait_for_unit("redis-peertube.service")
+
+    database.wait_for_open_port(5432)
+    database.wait_for_open_port(31638)
+
+    server.wait_for_unit("peertube.service")
+    server.wait_for_open_port(9000)
+
+    # Check if PeerTube is running
+    client.succeed("curl --fail http://peertube.local:9000/api/v1/config/about | jq -r '.instance.name' | grep 'PeerTube\ Test\ Server'")
+
+    # Check PeerTube CLI version
+    assert "${pkgs.peertube.version}" in server.succeed('su - peertube -s /bin/sh -c "peertube --version"')
+
+    client.shutdown()
+    server.shutdown()
+    database.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/phylactery.nix b/nixpkgs/nixos/tests/web-apps/phylactery.nix
new file mode 100644
index 000000000000..cf2689d2300d
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/phylactery.nix
@@ -0,0 +1,20 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "phylactery";
+
+  nodes.machine = { ... }: {
+    services.phylactery = rec {
+      enable = true;
+      port = 8080;
+      library = "/tmp";
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit('phylactery')
+    machine.wait_for_open_port(8080)
+    machine.wait_until_succeeds('curl localhost:8080')
+  '';
+
+  meta.maintainers = with lib.maintainers; [ McSinyx ];
+})
diff --git a/nixpkgs/nixos/tests/web-apps/pixelfed/default.nix b/nixpkgs/nixos/tests/web-apps/pixelfed/default.nix
new file mode 100644
index 000000000000..4464ebe43486
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/pixelfed/default.nix
@@ -0,0 +1,8 @@
+{ system ? builtins.currentSystem, handleTestOn }:
+let
+  supportedSystems = [ "x86_64-linux" "i686-linux" ];
+
+in
+{
+  standard = handleTestOn supportedSystems ./standard.nix { inherit system; };
+}
diff --git a/nixpkgs/nixos/tests/web-apps/pixelfed/standard.nix b/nixpkgs/nixos/tests/web-apps/pixelfed/standard.nix
new file mode 100644
index 000000000000..9260e27af960
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/pixelfed/standard.nix
@@ -0,0 +1,38 @@
+import ../../make-test-python.nix ({pkgs, ...}:
+{
+  name = "pixelfed-standard";
+  meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+  nodes = {
+    server = { pkgs, ... }: {
+      services.pixelfed = {
+        enable = true;
+        domain = "pixelfed.local";
+        # Configure NGINX.
+        nginx = {};
+        secretFile = (pkgs.writeText "secrets.env" ''
+          # Snakeoil secret, can be any random 32-chars secret via CSPRNG.
+          APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
+        '');
+        settings."FORCE_HTTPS_URLS" = false;
+      };
+    };
+  };
+
+  testScript = ''
+    # Wait for Pixelfed PHP pool
+    server.wait_for_unit("phpfpm-pixelfed.service")
+    # Wait for NGINX
+    server.wait_for_unit("nginx.service")
+    # Wait for HTTP port
+    server.wait_for_open_port(80)
+    # Access the homepage.
+    server.succeed("curl -H 'Host: pixelfed.local' http://localhost")
+    # Create an account
+    server.succeed("pixelfed-manage user:create --name=test --username=test --email=test@test.com --password=test")
+    # Create a OAuth token.
+    # TODO: figure out how to use it to send a image/toot
+    # server.succeed("pixelfed-manage passport:client --personal")
+    # server.succeed("curl -H 'Host: pixefed.local' -H 'Accept: application/json' -H 'Authorization: Bearer secret' -F'status'='test' http://localhost/api/v1/statuses")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-apps/pretalx.nix b/nixpkgs/nixos/tests/web-apps/pretalx.nix
new file mode 100644
index 000000000000..a226639b076b
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/pretalx.nix
@@ -0,0 +1,31 @@
+{ lib, ... }:
+
+{
+  name = "pretalx";
+  meta.maintainers = lib.teams.c3d2.members;
+
+  nodes = {
+    pretalx = {
+      networking.extraHosts = ''
+        127.0.0.1 talks.local
+      '';
+
+      services.pretalx = {
+        enable = true;
+        nginx.domain = "talks.local";
+        settings = {
+          site.url = "http://talks.local";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    pretalx.wait_for_unit("pretalx-web.service")
+    pretalx.wait_for_unit("pretalx-worker.service")
+
+    pretalx.wait_until_succeeds("curl -q --fail http://talks.local/orga/")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/web-apps/snipe-it.nix b/nixpkgs/nixos/tests/web-apps/snipe-it.nix
new file mode 100644
index 000000000000..123d7742056b
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/snipe-it.nix
@@ -0,0 +1,101 @@
+/*
+Snipe-IT NixOS test
+
+It covers the following scenario:
+- Installation
+- Backup and restore
+
+Scenarios NOT covered by this test (but perhaps in the future):
+- Sending and receiving emails
+*/
+{ pkgs, ... }: let
+  siteName = "NixOS Snipe-IT Test Instance";
+in {
+  name = "snipe-it";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ yayayayaka ];
+
+  nodes = {
+    snipeit = { ... }: {
+      services.snipe-it = {
+        enable = true;
+        appKeyFile = toString (pkgs.writeText "snipe-it-app-key" "uTqGUN5GUmUrh/zSAYmhyzRk62pnpXICyXv9eeITI8k=");
+        hostName = "localhost";
+        database.createLocally = true;
+        mail = {
+          driver = "smtp";
+          encryption = "tls";
+          host = "localhost";
+          port = 1025;
+          from.name = "Snipe-IT NixOS test";
+          from.address = "snipe-it@localhost";
+          replyTo.address = "snipe-it@localhost";
+          user = "snipe-it@localhost";
+          passwordFile = toString (pkgs.writeText "snipe-it-mail-pass" "a-secure-mail-password");
+        };
+      };
+    };
+  };
+
+  testScript = { nodes }: let
+    backupPath = "${nodes.snipeit.services.snipe-it.dataDir}/storage/app/backups";
+
+    # Snipe-IT has been installed successfully if the site name shows up on the login page
+    checkLoginPage = { shouldSucceed ? true }: ''
+      snipeit.${if shouldSucceed then "succeed" else "fail"}("""curl http://localhost/login | grep '${siteName}'""")
+    '';
+  in ''
+    start_all()
+
+    snipeit.wait_for_unit("nginx.service")
+    snipeit.wait_for_unit("snipe-it-setup.service")
+
+    # Create an admin user
+    snipeit.succeed(
+        """
+        snipe-it snipeit:create-admin \
+            --username="admin" \
+            --email="janedoe@localhost" \
+            --password="extremesecurepassword" \
+            --first_name="Jane" \
+            --last_name="Doe"
+        """
+    )
+
+    with subtest("Circumvent the pre-flight setup by just writing some settings into the database ourself"):
+        snipeit.succeed(
+            """
+            mysql -D ${nodes.snipeit.services.snipe-it.database.name} -e "INSERT INTO settings (id, user_id, site_name) VALUES ('1', '1', '${siteName}');"
+            """
+        )
+
+        # Usually these are generated during the pre-flight setup
+        snipeit.succeed("snipe-it passport:keys")
+
+
+    # Login page should now contain the configured site name
+    ${checkLoginPage {}}
+
+    with subtest("Test Backup and restore"):
+        snipeit.succeed("snipe-it snipeit:backup")
+
+        # One zip file should have been created
+        snipeit.succeed("""[ "$(ls -1 "${backupPath}" | wc -l)" -eq 1 ]""")
+
+        # Purge the state
+        snipeit.succeed("snipe-it migrate:fresh --force")
+
+        # Login page should disappear
+        ${checkLoginPage { shouldSucceed = false; }}
+
+        # Restore the state
+        snipeit.succeed(
+            """
+            snipe-it snipeit:restore --force $(find "${backupPath}/" -type f -name "*.zip")
+            """
+        )
+
+        # Login page should be back again
+        ${checkLoginPage {}}
+  '';
+}
diff --git a/nixpkgs/nixos/tests/web-apps/writefreely.nix b/nixpkgs/nixos/tests/web-apps/writefreely.nix
new file mode 100644
index 000000000000..ce614909706b
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-apps/writefreely.nix
@@ -0,0 +1,44 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../../.. { inherit system config; } }:
+
+with import ../../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  writefreelyTest = { name, type }:
+    makeTest {
+      name = "writefreely-${name}";
+
+      nodes.machine = { config, pkgs, ... }: {
+        services.writefreely = {
+          enable = true;
+          host = "localhost:3000";
+          admin.name = "nixos";
+
+          database = {
+            inherit type;
+            createLocally = type == "mysql";
+            passwordFile = pkgs.writeText "db-pass" "pass";
+          };
+
+          settings.server.port = 3000;
+        };
+      };
+
+      testScript = ''
+        start_all()
+        machine.wait_for_unit("writefreely.service")
+        machine.wait_for_open_port(3000)
+        machine.succeed("curl --fail http://localhost:3000")
+      '';
+    };
+in {
+  sqlite = writefreelyTest {
+    name = "sqlite";
+    type = "sqlite3";
+  };
+  mysql = writefreelyTest {
+    name = "mysql";
+    type = "mysql";
+  };
+}
diff --git a/nixpkgs/nixos/tests/web-servers/agate.nix b/nixpkgs/nixos/tests/web-servers/agate.nix
new file mode 100644
index 000000000000..0de27b6f7d8d
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-servers/agate.nix
@@ -0,0 +1,27 @@
+{ pkgs, lib, ... }:
+{
+  name = "agate";
+  meta = with lib.maintainers; { maintainers = [ jk ]; };
+
+  nodes = {
+    geminiserver = { pkgs, ... }: {
+      services.agate = {
+        enable = true;
+        hostnames = [ "localhost" ];
+        contentDir = pkgs.writeTextDir "index.gmi" ''
+          # Hello NixOS!
+        '';
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    geminiserver.wait_for_unit("agate")
+    geminiserver.wait_for_open_port(1965)
+
+    with subtest("check is serving over gemini"):
+      response = geminiserver.succeed("${pkgs.gemget}/bin/gemget --header -o - gemini://localhost:1965")
+      print(response)
+      assert "Hello NixOS!" in response
+  '';
+}
diff --git a/nixpkgs/nixos/tests/web-servers/stargazer.nix b/nixpkgs/nixos/tests/web-servers/stargazer.nix
new file mode 100644
index 000000000000..f56d1b8c9454
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-servers/stargazer.nix
@@ -0,0 +1,127 @@
+{ pkgs, lib, ... }:
+let
+  test_script = pkgs.stdenv.mkDerivation rec {
+    pname = "stargazer-test-script";
+    inherit (pkgs.stargazer) version src;
+    buildInputs = with pkgs; [ (python3.withPackages (ps: with ps; [ cryptography ])) ];
+    dontBuild = true;
+    doCheck = false;
+    installPhase = ''
+      mkdir -p $out/bin
+      cp scripts/gemini-diagnostics $out/bin/test
+    '';
+  };
+  test_env = pkgs.stdenv.mkDerivation rec {
+    pname = "stargazer-test-env";
+    inherit (pkgs.stargazer) version src;
+    buildPhase = ''
+      cc test_data/cgi-bin/loop.c -o test_data/cgi-bin/loop
+    '';
+    doCheck = false;
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+    '';
+  };
+  scgi_server = pkgs.stdenv.mkDerivation rec {
+    pname = "stargazer-test-scgi-server";
+    inherit (pkgs.stargazer) version src;
+    buildInputs = with pkgs; [ python3 ];
+    dontConfigure = true;
+    dontBuild = true;
+    doCheck = false;
+    installPhase = ''
+      mkdir -p $out/bin
+      cp scripts/scgi-server $out/bin/scgi-server
+    '';
+  };
+in
+{
+  name = "stargazer";
+  meta = with lib.maintainers; { maintainers = [ gaykitty ]; };
+
+  nodes = {
+    geminiserver = { pkgs, ... }: {
+      services.stargazer = {
+        enable = true;
+        connectionLogging = false;
+        requestTimeout = 1;
+        routes = [
+          {
+            route = "localhost";
+            root = "${test_env}/test_data/test_site";
+          }
+          {
+            route = "localhost=/en.gmi";
+            root = "${test_env}/test_data/test_site";
+            lang = "en";
+            charset = "ascii";
+          }
+          {
+            route = "localhost~/(.*).gemini";
+            root = "${test_env}/test_data/test_site";
+            rewrite = "\\1.gmi";
+            lang = "en";
+            charset = "ascii";
+          }
+          {
+            route = "localhost=/plain.txt";
+            root = "${test_env}/test_data/test_site";
+            lang = "en";
+            charset = "ascii";
+            cert-path = "/var/lib/gemini/certs/localhost.crt";
+            key-path = "/var/lib/gemini/certs/localhost.key";
+          }
+          {
+            route = "localhost:/cgi-bin";
+            root = "${test_env}/test_data";
+            cgi = true;
+            cgi-timeout = 5;
+          }
+          {
+            route = "localhost:/scgi";
+            scgi = true;
+            scgi-address = "127.0.0.1:1099";
+          }
+          {
+            route = "localhost=/root";
+            redirect = "..";
+            permanent = true;
+          }
+          {
+            route = "localhost=/priv.gmi";
+            root = "${test_env}/test_data/test_site";
+            client-cert = "${test_env}/test_data/client_cert/good.crt";
+          }
+          {
+            route = "example.com~(.*)";
+            redirect = "gemini://localhost";
+            rewrite = "\\1";
+          }
+          {
+            route = "localhost:/no-exist";
+            root = "./does_not_exist";
+          }
+        ];
+      };
+      systemd.services.scgi_server = {
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${scgi_server}/bin/scgi-server";
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    geminiserver.wait_for_unit("scgi_server")
+    geminiserver.wait_for_open_port(1099)
+    geminiserver.wait_for_unit("stargazer")
+    geminiserver.wait_for_open_port(1965)
+
+    with subtest("stargazer test suite"):
+      response = geminiserver.succeed("sh -c 'cd ${test_env}; ${test_script}/bin/test'")
+      print(response)
+  '';
+}
diff --git a/nixpkgs/nixos/tests/web-servers/static-web-server.nix b/nixpkgs/nixos/tests/web-servers/static-web-server.nix
new file mode 100644
index 000000000000..da1a9bdec5d2
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-servers/static-web-server.nix
@@ -0,0 +1,32 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } : {
+  name = "static-web-server";
+  meta = {
+    maintainers = with lib.maintainers; [ mac-chaffee ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    services.static-web-server = {
+      enable = true;
+      listen = "[::]:8080";
+      root = toString (pkgs.writeTextDir "nixos-test.html" ''
+        <h1>Hello NixOS!</h1>
+      '');
+      configuration = {
+        general = { directory-listing = true; };
+      };
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("static-web-server.socket")
+    machine.wait_for_open_port(8080)
+    # We don't use wait_until_succeeds() because we're testing socket
+    # activation which better work on the first request
+    response = machine.succeed("curl -fsS localhost:8080")
+    assert "nixos-test.html" in response, "The directory listing page did not include a link to our nixos-test.html file"
+    response = machine.succeed("curl -fsS localhost:8080/nixos-test.html")
+    assert "Hello NixOS!" in response
+    machine.wait_for_unit("static-web-server.service")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-servers/ttyd.nix b/nixpkgs/nixos/tests/web-servers/ttyd.nix
new file mode 100644
index 000000000000..b79a2032ec75
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-servers/ttyd.nix
@@ -0,0 +1,29 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "ttyd";
+  meta.maintainers = with lib.maintainers; [ stunkymonkey ];
+
+  nodes.readonly = { pkgs, ... }: {
+    services.ttyd = {
+      enable = true;
+      entrypoint = [ (lib.getExe pkgs.htop) ];
+      writeable = false;
+    };
+  };
+
+  nodes.writeable = { pkgs, ... }: {
+    services.ttyd = {
+      enable = true;
+      username = "foo";
+      passwordFile = pkgs.writeText "password" "bar";
+      writeable = true;
+    };
+  };
+
+  testScript = ''
+    for machine in [readonly, writeable]:
+      machine.wait_for_unit("ttyd.service")
+      machine.wait_for_open_port(7681)
+      response = machine.succeed("curl -vvv -u foo:bar -s -H 'Host: ttyd' http://127.0.0.1:7681/")
+      assert '<title>ttyd - Terminal</title>' in response, "Page didn't load successfully"
+  '';
+})
diff --git a/nixpkgs/nixos/tests/web-servers/unit-php.nix b/nixpkgs/nixos/tests/web-servers/unit-php.nix
new file mode 100644
index 000000000000..f0df371945e5
--- /dev/null
+++ b/nixpkgs/nixos/tests/web-servers/unit-php.nix
@@ -0,0 +1,52 @@
+import ../make-test-python.nix ({pkgs, ...}:
+let
+  testdir = pkgs.writeTextDir "www/info.php" "<?php phpinfo();";
+
+in {
+  name = "unit-php-test";
+  meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    services.unit = {
+      enable = true;
+      config = pkgs.lib.strings.toJSON {
+        listeners."*:9081".application = "php_81";
+        applications.php_81 = {
+          type = "php 8.1";
+          processes = 1;
+          user = "testuser";
+          group = "testgroup";
+          root = "${testdir}/www";
+          index = "info.php";
+          options.file = "${pkgs.unit.usedPhp81}/lib/php.ini";
+        };
+      };
+    };
+    users = {
+      users.testuser = {
+        isSystemUser = true;
+        uid = 1080;
+        group = "testgroup";
+      };
+      groups.testgroup = {
+        gid = 1080;
+      };
+    };
+  };
+  testScript = ''
+    machine.start()
+
+    machine.wait_for_unit("unit.service")
+    machine.wait_for_open_port(9081)
+
+    # Check so we get an evaluated PHP back
+    response = machine.succeed("curl -f -vvv -s http://127.0.0.1:9081/")
+    assert "PHP Version ${pkgs.unit.usedPhp81.version}" in response, "PHP version not detected"
+
+    # Check so we have database and some other extensions loaded
+    for ext in ["json", "opcache", "pdo_mysql", "pdo_pgsql", "pdo_sqlite"]:
+        assert ext in response, f"Missing {ext} extension"
+
+    machine.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/webhook.nix b/nixpkgs/nixos/tests/webhook.nix
new file mode 100644
index 000000000000..ed7051408640
--- /dev/null
+++ b/nixpkgs/nixos/tests/webhook.nix
@@ -0,0 +1,65 @@
+{ pkgs, ... }:
+let
+  forwardedPort = 19000;
+  internalPort = 9000;
+in
+{
+  name = "webhook";
+
+  nodes = {
+    webhookMachine = { pkgs, ... }: {
+      virtualisation.forwardPorts = [{
+        host.port = forwardedPort;
+        guest.port = internalPort;
+      }];
+      services.webhook = {
+        enable = true;
+        port = internalPort;
+        openFirewall = true;
+        hooks = {
+          echo = {
+            execute-command = "echo";
+            response-message = "Webhook is reachable!";
+          };
+        };
+        hooksTemplated = {
+          echoTemplate = ''
+            {
+              "id": "echo-template",
+              "execute-command": "echo",
+              "response-message": "{{ getenv "WEBHOOK_MESSAGE" }}"
+            }
+          '';
+        };
+        environment.WEBHOOK_MESSAGE = "Templates are working!";
+      };
+    };
+  };
+
+  extraPythonPackages = p: [
+    p.requests
+    p.types-requests
+  ];
+
+  testScript = { nodes, ... }: ''
+    import requests
+    webhookMachine.wait_for_unit("webhook")
+    webhookMachine.wait_for_open_port(${toString internalPort})
+
+    with subtest("Check that webhooks can be called externally"):
+      response = requests.get("http://localhost:${toString forwardedPort}/hooks/echo")
+      print(f"Response code: {response.status_code}")
+      print("Response: %r" % response.content)
+
+      assert response.status_code == 200
+      assert response.content == b"Webhook is reachable!"
+
+    with subtest("Check that templated webhooks can be called externally"):
+      response = requests.get("http://localhost:${toString forwardedPort}/hooks/echo-template")
+      print(f"Response code: {response.status_code}")
+      print("Response: %r" % response.content)
+
+      assert response.status_code == 200
+      assert response.content == b"Templates are working!"
+  '';
+}
diff --git a/nixpkgs/nixos/tests/wiki-js.nix b/nixpkgs/nixos/tests/wiki-js.nix
new file mode 100644
index 000000000000..8b3c51935a6c
--- /dev/null
+++ b/nixpkgs/nixos/tests/wiki-js.nix
@@ -0,0 +1,153 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "wiki-js";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ma27 ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.memorySize = 2047;
+    services.wiki-js = {
+      enable = true;
+      settings.db.host = "/run/postgresql";
+      settings.db.user = "wiki-js";
+      settings.db.db = "wiki-js";
+      settings.logLevel = "debug";
+    };
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "wiki-js" ];
+      ensureUsers = [
+        { name = "wiki-js";
+          ensureDBOwnership = true;
+        }
+      ];
+    };
+    systemd.services.wiki-js = {
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+    };
+    environment.systemPackages = with pkgs; [ jq ];
+  };
+
+  testScript = let
+    payloads.finalize = pkgs.writeText "finalize.json" (builtins.toJSON {
+      adminEmail = "webmaster@example.com";
+      adminPassword = "notapassword";
+      adminPasswordConfirm = "notapassword";
+      siteUrl = "http://localhost:3000";
+      telemetry = false;
+    });
+    payloads.login = pkgs.writeText "login.json" (builtins.toJSON [{
+      operationName = null;
+      extensions = {};
+      query = ''
+        mutation ($username: String!, $password: String!, $strategy: String!) {
+          authentication {
+            login(username: $username, password: $password, strategy: $strategy) {
+              responseResult {
+                succeeded
+                errorCode
+                slug
+                message
+                __typename
+              }
+              jwt
+              mustChangePwd
+              mustProvideTFA
+              mustSetupTFA
+              continuationToken
+              redirect
+              tfaQRImage
+              __typename
+            }
+            __typename
+          }
+        }
+      '';
+      variables = {
+        password = "notapassword";
+        strategy = "local";
+        username = "webmaster@example.com";
+      };
+    }]);
+    payloads.content = pkgs.writeText "content.json" (builtins.toJSON [{
+      extensions = {};
+      operationName = null;
+      query = ''
+        mutation ($content: String!, $description: String!, $editor: String!, $isPrivate: Boolean!, $isPublished: Boolean!, $locale: String!, $path: String!, $publishEndDate: Date, $publishStartDate: Date, $scriptCss: String, $scriptJs: String, $tags: [String]!, $title: String!) {
+          pages {
+            create(content: $content, description: $description, editor: $editor, isPrivate: $isPrivate, isPublished: $isPublished, locale: $locale, path: $path, publishEndDate: $publishEndDate, publishStartDate: $publishStartDate, scriptCss: $scriptCss, scriptJs: $scriptJs, tags: $tags, title: $title) {
+              responseResult {
+                succeeded
+                errorCode
+                slug
+                message
+                __typename
+              }
+              page {
+                id
+                updatedAt
+                __typename
+              }
+              __typename
+            }
+            __typename
+          }
+        }
+      '';
+      variables = {
+        content = "# Header\n\nHello world!";
+        description = "";
+        editor = "markdown";
+        isPrivate = false;
+        isPublished = true;
+        locale = "en";
+        path = "home";
+        publishEndDate = "";
+        publishStartDate = "";
+        scriptCss = "";
+        scriptJs = "";
+        tags = [];
+        title = "Hello world";
+      };
+    }]);
+  in ''
+    machine.start()
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_open_port(3000)
+
+    machine.succeed("curl -sSf localhost:3000")
+
+    with subtest("Setup"):
+        result = machine.succeed(
+            "curl -sSf localhost:3000/finalize -X POST -d "
+            + "@${payloads.finalize} -H 'Content-Type: application/json' "
+            + "| jq .ok | xargs echo"
+        )
+        assert result.strip() == "true", f"Expected true, got {result}"
+
+        # During the setup the service gets restarted, so we use this
+        # to check if the setup is done.
+        machine.wait_until_fails("curl -sSf localhost:3000")
+        machine.wait_until_succeeds("curl -sSf localhost:3000")
+
+    with subtest("Base functionality"):
+        auth = machine.succeed(
+            "curl -sSf localhost:3000/graphql -X POST "
+            + "-d @${payloads.login} -H 'Content-Type: application/json' "
+            + "| jq '.[0].data.authentication.login.jwt' | xargs echo"
+        ).strip()
+
+        assert auth
+
+        create = machine.succeed(
+            "curl -sSf localhost:3000/graphql -X POST "
+            + "-d @${payloads.content} -H 'Content-Type: application/json' "
+            + f"-H 'Authorization: Bearer {auth}' "
+            + "| jq '.[0].data.pages.create.responseResult.succeeded'|xargs echo"
+        )
+        assert create.strip() == "true", f"Expected true, got {create}"
+
+    machine.shutdown()
+  '';
+})
diff --git a/nixpkgs/nixos/tests/wine.nix b/nixpkgs/nixos/tests/wine.nix
new file mode 100644
index 000000000000..7cbe7ac94f1e
--- /dev/null
+++ b/nixpkgs/nixos/tests/wine.nix
@@ -0,0 +1,51 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (pkgs.lib) concatMapStrings listToAttrs optionals optionalString;
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+
+  hello32 = "${pkgs.pkgsCross.mingw32.hello}/bin/hello.exe";
+  hello64 = "${pkgs.pkgsCross.mingwW64.hello}/bin/hello.exe";
+
+  makeWineTest = packageSet: exes: variant: rec {
+    name = "${packageSet}-${variant}";
+    value = makeTest {
+      inherit name;
+      meta = with pkgs.lib.maintainers; { maintainers = [ chkno ]; };
+
+      nodes.machine = { pkgs, ... }: {
+        environment.systemPackages = [ pkgs."${packageSet}"."${variant}" ];
+        virtualisation.diskSize = 800;
+      };
+
+      testScript = ''
+        machine.wait_for_unit("multi-user.target")
+        ${concatMapStrings (exe: ''
+          greeting = machine.succeed(
+              "bash -c 'wine ${exe} 2> >(tee wine-stderr >&2)'"
+          )
+          assert 'Hello, world!' in greeting
+        ''
+        # only the full version contains Gecko, but the error is not printed reliably in other variants
+        + optionalString (variant == "full") ''
+          machine.fail(
+              "fgrep 'Could not find Wine Gecko. HTML rendering will be disabled.' wine-stderr"
+          )
+        '') exes}
+      '';
+    };
+  };
+
+  variants = [ "base" "full" "minimal" "staging" "unstable" "wayland" ];
+
+in
+listToAttrs (
+  map (makeWineTest "winePackages" [ hello32 ]) variants
+  ++ optionals pkgs.stdenv.is64bit
+    (map (makeWineTest "wineWowPackages" [ hello32 hello64 ])
+         # This wayland combination times out after spending many hours.
+         # https://hydra.nixos.org/job/nixos/trunk-combined/nixos.tests.wine.wineWowPackages-wayland.x86_64-linux
+         (pkgs.lib.remove "wayland" variants))
+)
diff --git a/nixpkgs/nixos/tests/wireguard/basic.nix b/nixpkgs/nixos/tests/wireguard/basic.nix
new file mode 100644
index 000000000000..96b0a681c364
--- /dev/null
+++ b/nixpkgs/nixos/tests/wireguard/basic.nix
@@ -0,0 +1,73 @@
+import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, ...} :
+  let
+    wg-snakeoil-keys = import ./snakeoil-keys.nix;
+    peer = (import ./make-peer.nix) { inherit lib; };
+  in
+  {
+    name = "wireguard";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ ma27 ];
+    };
+
+    nodes = {
+      peer0 = peer {
+        ip4 = "192.168.0.1";
+        ip6 = "fd00::1";
+        extraConfig = {
+          boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+          networking.firewall.allowedUDPPorts = [ 23542 ];
+          networking.wireguard.interfaces.wg0 = {
+            ips = [ "10.23.42.1/32" "fc00::1/128" ];
+            listenPort = 23542;
+
+            inherit (wg-snakeoil-keys.peer0) privateKey;
+
+            peers = lib.singleton {
+              allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
+
+              inherit (wg-snakeoil-keys.peer1) publicKey;
+            };
+          };
+        };
+      };
+
+      peer1 = peer {
+        ip4 = "192.168.0.2";
+        ip6 = "fd00::2";
+        extraConfig = {
+          boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+          networking.wireguard.interfaces.wg0 = {
+            ips = [ "10.23.42.2/32" "fc00::2/128" ];
+            listenPort = 23542;
+            allowedIPsAsRoutes = false;
+
+            inherit (wg-snakeoil-keys.peer1) privateKey;
+
+            peers = lib.singleton {
+              allowedIPs = [ "0.0.0.0/0" "::/0" ];
+              endpoint = "192.168.0.1:23542";
+              persistentKeepalive = 25;
+
+              inherit (wg-snakeoil-keys.peer0) publicKey;
+            };
+
+            postSetup = let inherit (pkgs) iproute2; in ''
+              ${iproute2}/bin/ip route replace 10.23.42.1/32 dev wg0
+              ${iproute2}/bin/ip route replace fc00::1/128 dev wg0
+            '';
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      peer0.wait_for_unit("wireguard-wg0.service")
+      peer1.wait_for_unit("wireguard-wg0.service")
+
+      peer1.succeed("ping -c5 fc00::1")
+      peer1.succeed("ping -c5 10.23.42.1")
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/wireguard/default.nix b/nixpkgs/nixos/tests/wireguard/default.nix
new file mode 100644
index 000000000000..c30f1b74770b
--- /dev/null
+++ b/nixpkgs/nixos/tests/wireguard/default.nix
@@ -0,0 +1,28 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+, kernelVersionsToTest ? [ "5.4" "latest" ]
+}:
+
+with pkgs.lib;
+
+let
+  tests = let callTest = p: args: import p ({ inherit system pkgs; } // args); in {
+    basic = callTest ./basic.nix;
+    namespaces = callTest ./namespaces.nix;
+    wg-quick = callTest ./wg-quick.nix;
+    wg-quick-nftables = args: callTest ./wg-quick.nix ({ nftables = true; } // args);
+    generated = callTest ./generated.nix;
+  };
+in
+
+listToAttrs (
+  flip concatMap kernelVersionsToTest (version:
+    let
+      v' = replaceStrings [ "." ] [ "_" ] version;
+    in
+    flip mapAttrsToList tests (name: test:
+      nameValuePair "wireguard-${name}-linux-${v'}" (test { kernelPackages = pkgs."linuxPackages_${v'}"; })
+    )
+  )
+)
diff --git a/nixpkgs/nixos/tests/wireguard/generated.nix b/nixpkgs/nixos/tests/wireguard/generated.nix
new file mode 100644
index 000000000000..c58f7a75071e
--- /dev/null
+++ b/nixpkgs/nixos/tests/wireguard/generated.nix
@@ -0,0 +1,63 @@
+import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, ... } : {
+  name = "wireguard-generated";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ma27 grahamc ];
+  };
+
+  nodes = {
+    peer1 = {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+      networking.firewall.allowedUDPPorts = [ 12345 ];
+      networking.wireguard.interfaces.wg0 = {
+        ips = [ "10.10.10.1/24" ];
+        listenPort = 12345;
+        privateKeyFile = "/etc/wireguard/private";
+        generatePrivateKeyFile = true;
+
+      };
+    };
+
+    peer2 = {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+      networking.firewall.allowedUDPPorts = [ 12345 ];
+      networking.wireguard.interfaces.wg0 = {
+        ips = [ "10.10.10.2/24" ];
+        listenPort = 12345;
+        privateKeyFile = "/etc/wireguard/private";
+        generatePrivateKeyFile = true;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    peer1.wait_for_unit("wireguard-wg0.service")
+    peer2.wait_for_unit("wireguard-wg0.service")
+
+    retcode, peer1pubkey = peer1.execute("wg pubkey < /etc/wireguard/private")
+    if retcode != 0:
+        raise Exception("Could not read public key from peer1")
+
+    retcode, peer2pubkey = peer2.execute("wg pubkey < /etc/wireguard/private")
+    if retcode != 0:
+        raise Exception("Could not read public key from peer2")
+
+    peer1.succeed(
+        "wg set wg0 peer {} allowed-ips 10.10.10.2/32 endpoint 192.168.1.2:12345 persistent-keepalive 1".format(
+            peer2pubkey.strip()
+        )
+    )
+    peer1.succeed("ip route replace 10.10.10.2/32 dev wg0 table main")
+
+    peer2.succeed(
+        "wg set wg0 peer {} allowed-ips 10.10.10.1/32 endpoint 192.168.1.1:12345 persistent-keepalive 1".format(
+            peer1pubkey.strip()
+        )
+    )
+    peer2.succeed("ip route replace 10.10.10.1/32 dev wg0 table main")
+
+    peer1.succeed("ping -c1 10.10.10.2")
+    peer2.succeed("ping -c1 10.10.10.1")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/wireguard/make-peer.nix b/nixpkgs/nixos/tests/wireguard/make-peer.nix
new file mode 100644
index 000000000000..d2740549738b
--- /dev/null
+++ b/nixpkgs/nixos/tests/wireguard/make-peer.nix
@@ -0,0 +1,23 @@
+{ lib, ... }: { ip4, ip6, extraConfig }:
+lib.mkMerge [
+  {
+    boot.kernel.sysctl = {
+      "net.ipv6.conf.all.forwarding" = "1";
+      "net.ipv6.conf.default.forwarding" = "1";
+      "net.ipv4.ip_forward" = "1";
+    };
+
+    networking.useDHCP = false;
+    networking.interfaces.eth1 = {
+      ipv4.addresses = [{
+        address = ip4;
+        prefixLength = 24;
+      }];
+      ipv6.addresses = [{
+        address = ip6;
+        prefixLength = 64;
+      }];
+    };
+  }
+  extraConfig
+]
diff --git a/nixpkgs/nixos/tests/wireguard/namespaces.nix b/nixpkgs/nixos/tests/wireguard/namespaces.nix
new file mode 100644
index 000000000000..d0eb009e1107
--- /dev/null
+++ b/nixpkgs/nixos/tests/wireguard/namespaces.nix
@@ -0,0 +1,83 @@
+let
+  listenPort = 12345;
+  socketNamespace = "foo";
+  interfaceNamespace = "bar";
+  node = {
+    networking.wireguard.interfaces.wg0 = {
+      listenPort = listenPort;
+      ips = [ "10.10.10.1/24" ];
+      privateKeyFile = "/etc/wireguard/private";
+      generatePrivateKeyFile = true;
+    };
+  };
+
+in
+
+import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, ... } : {
+  name = "wireguard-with-namespaces";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ asymmetric ];
+  };
+
+  nodes = {
+    # interface should be created in the socketNamespace
+    # and not moved from there
+    peer0 = pkgs.lib.attrsets.recursiveUpdate node {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+      networking.wireguard.interfaces.wg0 = {
+        preSetup = ''
+          ip netns add ${socketNamespace}
+        '';
+        inherit socketNamespace;
+      };
+    };
+    # interface should be created in the init namespace
+    # and moved to the interfaceNamespace
+    peer1 = pkgs.lib.attrsets.recursiveUpdate node {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+      networking.wireguard.interfaces.wg0 = {
+        preSetup = ''
+          ip netns add ${interfaceNamespace}
+        '';
+        mtu = 1280;
+        inherit interfaceNamespace;
+      };
+    };
+    # interface should be created in the socketNamespace
+    # and moved to the interfaceNamespace
+    peer2 = pkgs.lib.attrsets.recursiveUpdate node {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+      networking.wireguard.interfaces.wg0 = {
+        preSetup = ''
+          ip netns add ${socketNamespace}
+          ip netns add ${interfaceNamespace}
+        '';
+        inherit socketNamespace interfaceNamespace;
+      };
+    };
+    # interface should be created in the socketNamespace
+    # and moved to the init namespace
+    peer3 = pkgs.lib.attrsets.recursiveUpdate node {
+      boot = lib.mkIf (kernelPackages != null) { inherit kernelPackages; };
+      networking.wireguard.interfaces.wg0 = {
+        preSetup = ''
+          ip netns add ${socketNamespace}
+        '';
+        inherit socketNamespace;
+        interfaceNamespace = "init";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    for machine in peer0, peer1, peer2, peer3:
+        machine.wait_for_unit("wireguard-wg0.service")
+
+    peer0.succeed("ip -n ${socketNamespace} link show wg0")
+    peer1.succeed("ip -n ${interfaceNamespace} link show wg0")
+    peer2.succeed("ip -n ${interfaceNamespace} link show wg0")
+    peer3.succeed("ip link show wg0")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix b/nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix
new file mode 100644
index 000000000000..c979f0e0c8a9
--- /dev/null
+++ b/nixpkgs/nixos/tests/wireguard/snakeoil-keys.nix
@@ -0,0 +1,12 @@
+{
+  peer0 = {
+    privateKey = "OPuVRS2T0/AtHDp3PXkNuLQYDiqJaBEEnYe42BSnJnQ=";
+    publicKey = "IujkG119YPr2cVQzJkSLYCdjpHIDjvr/qH1w1tdKswY=";
+  };
+
+  peer1 = {
+    privateKey = "uO8JVo/sanx2DOM0L9GUEtzKZ82RGkRnYgpaYc7iXmg=";
+    # readFile'd keys may have trailing newlines, emulate this
+    publicKey = "Ks9yRJIi/0vYgRmn14mIOQRwkcUGBujYINbMpik2SBI=\n";
+  };
+}
diff --git a/nixpkgs/nixos/tests/wireguard/wg-quick.nix b/nixpkgs/nixos/tests/wireguard/wg-quick.nix
new file mode 100644
index 000000000000..ec2b8d7f2d9d
--- /dev/null
+++ b/nixpkgs/nixos/tests/wireguard/wg-quick.nix
@@ -0,0 +1,80 @@
+import ../make-test-python.nix ({ pkgs, lib, kernelPackages ? null, nftables ? false, ... }:
+  let
+    wg-snakeoil-keys = import ./snakeoil-keys.nix;
+    peer = import ./make-peer.nix { inherit lib; };
+    commonConfig = {
+      boot.kernelPackages = lib.mkIf (kernelPackages != null) kernelPackages;
+      networking.nftables.enable = nftables;
+      # Make sure iptables doesn't work with nftables enabled
+      boot.blacklistedKernelModules = lib.mkIf nftables [ "nft_compat" ];
+    };
+  in
+  {
+    name = "wg-quick";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ d-xo ];
+    };
+
+    nodes = {
+      peer0 = peer {
+        ip4 = "192.168.0.1";
+        ip6 = "fd00::1";
+        extraConfig = lib.mkMerge [
+          commonConfig
+          {
+            networking.firewall.allowedUDPPorts = [ 23542 ];
+            networking.wg-quick.interfaces.wg0 = {
+              address = [ "10.23.42.1/32" "fc00::1/128" ];
+              listenPort = 23542;
+
+              inherit (wg-snakeoil-keys.peer0) privateKey;
+
+              peers = lib.singleton {
+                allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
+
+                inherit (wg-snakeoil-keys.peer1) publicKey;
+              };
+
+              dns = [ "10.23.42.2" "fc00::2" "wg0" ];
+            };
+          }
+        ];
+      };
+
+      peer1 = peer {
+        ip4 = "192.168.0.2";
+        ip6 = "fd00::2";
+        extraConfig = lib.mkMerge [
+          commonConfig
+          {
+            networking.useNetworkd = true;
+            networking.wg-quick.interfaces.wg0 = {
+              address = [ "10.23.42.2/32" "fc00::2/128" ];
+              inherit (wg-snakeoil-keys.peer1) privateKey;
+
+              peers = lib.singleton {
+                allowedIPs = [ "0.0.0.0/0" "::/0" ];
+                endpoint = "192.168.0.1:23542";
+                persistentKeepalive = 25;
+
+                inherit (wg-snakeoil-keys.peer0) publicKey;
+              };
+
+              dns = [ "10.23.42.1" "fc00::1" "wg0" ];
+            };
+          }
+        ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      peer0.wait_for_unit("wg-quick-wg0.service")
+      peer1.wait_for_unit("wg-quick-wg0.service")
+
+      peer1.succeed("ping -c5 fc00::1")
+      peer1.succeed("ping -c5 10.23.42.1")
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/without-nix.nix b/nixpkgs/nixos/tests/without-nix.nix
new file mode 100644
index 000000000000..b21e9f2844f5
--- /dev/null
+++ b/nixpkgs/nixos/tests/without-nix.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "without-nix";
+  meta = with lib.maintainers; {
+    maintainers = [ ericson2314 ];
+  };
+
+  nodes.machine = { ... }: {
+    nix.enable = false;
+    nixpkgs.overlays = [
+      (self: super: {
+        nix = throw "don't want to use pkgs.nix";
+        nixVersions = lib.mapAttrs (k: throw "don't want to use pkgs.nixVersions.${k}") super.nixVersions;
+        # aliases, some deprecated
+        nix_2_3 = throw "don't want to use pkgs.nix_2_3";
+        nix_2_4 = throw "don't want to use pkgs.nix_2_4";
+        nix_2_5 = throw "don't want to use pkgs.nix_2_5";
+        nix_2_6 = throw "don't want to use pkgs.nix_2_6";
+        nixFlakes = throw "don't want to use pkgs.nixFlakes";
+        nixStable = throw "don't want to use pkgs.nixStable";
+        nixUnstable = throw "don't want to use pkgs.nixUnstable";
+        nixStatic = throw "don't want to use pkgs.nixStatic";
+      })
+    ];
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.succeed("which which")
+    machine.fail("which nix")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/wmderland.nix b/nixpkgs/nixos/tests/wmderland.nix
new file mode 100644
index 000000000000..ebfd443763e1
--- /dev/null
+++ b/nixpkgs/nixos/tests/wmderland.nix
@@ -0,0 +1,54 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "wmderland";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ takagiy ];
+  };
+
+  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";
+    services.xserver.windowManager.wmderland.enable = true;
+
+    systemd.services.setupWmderlandConfig = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "multi-user.target" ];
+      environment = {
+        HOME = "/home/alice";
+      };
+      unitConfig = {
+        type = "oneshot";
+        RemainAfterExit = true;
+        user = "alice";
+      };
+      script = let
+        config = pkgs.writeText "config" ''
+             set $Mod = Mod1
+             bindsym $Mod+Return exec ${pkgs.xterm}/bin/xterm -cm -pc
+        '';
+      in ''
+        mkdir -p $HOME/.config/wmderland
+        cp ${config} $HOME/.config/wmderland/config
+      '';
+    };
+  };
+
+  testScript = { ... }: ''
+    with subtest("ensure x starts"):
+        machine.wait_for_x()
+        machine.wait_for_file("/home/alice/.Xauthority")
+        machine.succeed("xauth merge ~alice/.Xauthority")
+
+    with subtest("ensure we can open a new terminal"):
+        machine.send_key("alt-ret")
+        machine.wait_until_succeeds("pgrep xterm")
+        machine.wait_for_window(r"alice.*?machine")
+        machine.screenshot("terminal")
+
+    with subtest("ensure we can communicate through ipc with wmderlandc"):
+        # Kills the previously open xterm
+        machine.succeed("pgrep xterm")
+        machine.execute("DISPLAY=:0 wmderlandc kill")
+        machine.fail("pgrep xterm")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/wordpress.nix b/nixpkgs/nixos/tests/wordpress.nix
new file mode 100644
index 000000000000..592af9a094f1
--- /dev/null
+++ b/nixpkgs/nixos/tests/wordpress.nix
@@ -0,0 +1,101 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+rec {
+  name = "wordpress";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [
+      flokli
+      grahamc # under duress!
+      mmilata
+    ];
+  };
+
+  nodes = lib.foldl (a: version: let
+    package = pkgs."wordpress${version}";
+  in a // {
+    "wp${version}_httpd" = _: {
+      services.httpd.adminAddr = "webmaster@site.local";
+      services.httpd.logPerVirtualHost = true;
+
+      services.wordpress.webserver = "httpd";
+      services.wordpress.sites = {
+        "site1.local" = {
+          database.tablePrefix = "site1_";
+          inherit package;
+        };
+        "site2.local" = {
+          database.tablePrefix = "site2_";
+          inherit package;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+    };
+
+    "wp${version}_nginx" = _: {
+      services.wordpress.webserver = "nginx";
+      services.wordpress.sites = {
+        "site1.local" = {
+          database.tablePrefix = "site1_";
+          inherit package;
+        };
+        "site2.local" = {
+          database.tablePrefix = "site2_";
+          inherit package;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+    };
+
+    "wp${version}_caddy" = _: {
+      services.wordpress.webserver = "caddy";
+      services.wordpress.sites = {
+        "site1.local" = {
+          database.tablePrefix = "site1_";
+          inherit package;
+        };
+        "site2.local" = {
+          database.tablePrefix = "site2_";
+          inherit package;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+    };
+  }) {} [
+    "6_3" "6_4"
+  ];
+
+  testScript = ''
+    import re
+
+    start_all()
+
+    ${lib.concatStrings (lib.mapAttrsToList (name: value: ''
+      ${name}.wait_for_unit("${(value null).services.wordpress.webserver}")
+    '') nodes)}
+
+    site_names = ["site1.local", "site2.local"]
+
+    for machine in (${lib.concatStringsSep ", " (builtins.attrNames nodes)}):
+        for site_name in site_names:
+            machine.wait_for_unit(f"phpfpm-wordpress-{site_name}")
+
+            with subtest("website returns welcome screen"):
+                assert "Welcome to the famous" in machine.succeed(f"curl -L {site_name}")
+
+            with subtest("wordpress-init went through"):
+                info = machine.get_unit_info(f"wordpress-init-{site_name}")
+                assert info["Result"] == "success"
+
+            with subtest("secret keys are set"):
+                pattern = re.compile(r"^define.*NONCE_SALT.{64,};$", re.MULTILINE)
+                assert pattern.search(
+                    machine.succeed(f"cat /var/lib/wordpress/{site_name}/secret-keys.php")
+                )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/wpa_supplicant.nix b/nixpkgs/nixos/tests/wpa_supplicant.nix
new file mode 100644
index 000000000000..8c701ca7d5f7
--- /dev/null
+++ b/nixpkgs/nixos/tests/wpa_supplicant.nix
@@ -0,0 +1,210 @@
+import ./make-test-python.nix ({ pkgs, lib, ...}:
+{
+  name = "wpa_supplicant";
+  meta = with lib.maintainers; {
+    maintainers = [ oddlama rnhmjoj ];
+  };
+
+  nodes = let
+    machineWithHostapd = extraConfigModule: { ... }: {
+      imports = [
+        ../modules/profiles/minimal.nix
+        extraConfigModule
+      ];
+
+      # add a virtual wlan interface
+      boot.kernelModules = [ "mac80211_hwsim" ];
+
+      # wireless access point
+      services.hostapd = {
+        enable = true;
+        radios.wlan0 = {
+          band = "2g";
+          countryCode = "US";
+          networks = {
+            wlan0 = {
+              ssid = "nixos-test-sae";
+              authentication = {
+                mode = "wpa3-sae";
+                saePasswords = [ { password = "reproducibility"; } ];
+              };
+              bssid = "02:00:00:00:00:00";
+            };
+            wlan0-1 = {
+              ssid = "nixos-test-mixed";
+              authentication = {
+                mode = "wpa3-sae-transition";
+                saeAddToMacAllow = true;
+                saePasswordsFile = pkgs.writeText "password" "reproducibility";
+                wpaPasswordFile = pkgs.writeText "password" "reproducibility";
+              };
+              bssid = "02:00:00:00:00:01";
+            };
+            wlan0-2 = {
+              ssid = "nixos-test-wpa2";
+              authentication = {
+                mode = "wpa2-sha256";
+                wpaPassword = "reproducibility";
+              };
+              bssid = "02:00:00:00:00:02";
+            };
+          };
+        };
+      };
+
+      # wireless client
+      networking.wireless = {
+        # the override is needed because the wifi is
+        # disabled with mkVMOverride in qemu-vm.nix.
+        enable = lib.mkOverride 0 true;
+        userControlled.enable = true;
+        interfaces = [ "wlan1" ];
+        fallbackToWPA2 = lib.mkDefault true;
+
+        # networks will be added on-demand below for the specific
+        # network that should be tested
+
+        # secrets
+        environmentFile = pkgs.writeText "wpa-secrets" ''
+          PSK_NIXOS_TEST="reproducibility"
+        '';
+      };
+    };
+  in {
+    basic = { ... }: {
+      imports = [ ../modules/profiles/minimal.nix ];
+
+      # add a virtual wlan interface
+      boot.kernelModules = [ "mac80211_hwsim" ];
+
+      # wireless client
+      networking.wireless = {
+        # the override is needed because the wifi is
+        # disabled with mkVMOverride in qemu-vm.nix.
+        enable = lib.mkOverride 0 true;
+        userControlled.enable = true;
+        interfaces = [ "wlan1" ];
+        fallbackToWPA2 = true;
+
+        networks = {
+          # test WPA2 fallback
+          mixed-wpa = {
+            psk = "password";
+            authProtocols = [ "WPA-PSK" "SAE" ];
+          };
+          sae-only = {
+            psk = "password";
+            authProtocols = [ "SAE" ];
+          };
+
+          # secrets substitution test cases
+          test1.psk = "@PSK_VALID@";              # should be replaced
+          test2.psk = "@PSK_SPECIAL@";            # should be replaced
+          test3.psk = "@PSK_MISSING@";            # should not be replaced
+          test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced
+        };
+
+        # secrets
+        environmentFile = pkgs.writeText "wpa-secrets" ''
+          PSK_VALID="S0m3BadP4ssw0rd";
+          # taken from https://github.com/minimaxir/big-list-of-naughty-strings
+          PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~";
+        '';
+      };
+    };
+
+    # Test connecting to the SAE-only hotspot using SAE
+    machineSae = machineWithHostapd {
+      networking.wireless = {
+        fallbackToWPA2 = false;
+        networks.nixos-test-sae = {
+          psk = "@PSK_NIXOS_TEST@";
+          authProtocols = [ "SAE" ];
+        };
+      };
+    };
+
+    # Test connecting to the SAE and WPA2 mixed hotspot using SAE
+    machineMixedUsingSae = machineWithHostapd {
+      networking.wireless = {
+        fallbackToWPA2 = false;
+        networks.nixos-test-mixed = {
+          psk = "@PSK_NIXOS_TEST@";
+          authProtocols = [ "SAE" ];
+        };
+      };
+    };
+
+    # Test connecting to the SAE and WPA2 mixed hotspot using WPA2
+    machineMixedUsingWpa2 = machineWithHostapd {
+      networking.wireless = {
+        fallbackToWPA2 = true;
+        networks.nixos-test-mixed = {
+          psk = "@PSK_NIXOS_TEST@";
+          authProtocols = [ "WPA-PSK-SHA256" ];
+        };
+      };
+    };
+
+    # Test connecting to the WPA2 legacy hotspot using WPA2
+    machineWpa2 = machineWithHostapd {
+      networking.wireless = {
+        fallbackToWPA2 = true;
+        networks.nixos-test-wpa2 = {
+          psk = "@PSK_NIXOS_TEST@";
+          authProtocols = [ "WPA-PSK-SHA256" ];
+        };
+      };
+    };
+  };
+
+  testScript =
+    ''
+      config_file = "/run/wpa_supplicant/wpa_supplicant.conf"
+
+      with subtest("Configuration file is inaccessible to other users"):
+          basic.wait_for_file(config_file)
+          basic.fail(f"sudo -u nobody ls {config_file}")
+
+      with subtest("Secrets variables have been substituted"):
+          basic.fail(f"grep -q @PSK_VALID@ {config_file}")
+          basic.fail(f"grep -q @PSK_SPECIAL@ {config_file}")
+          basic.succeed(f"grep -q @PSK_MISSING@ {config_file}")
+          basic.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}")
+
+      with subtest("WPA2 fallbacks have been generated"):
+          assert int(basic.succeed(f"grep -c sae-only {config_file}")) == 1
+          assert int(basic.succeed(f"grep -c mixed-wpa {config_file}")) == 2
+
+      # save file for manual inspection
+      basic.copy_from_vm(config_file)
+
+      with subtest("Daemon is running and accepting connections"):
+          basic.wait_for_unit("wpa_supplicant-wlan1.service")
+          status = basic.succeed("wpa_cli -i wlan1 status")
+          assert "Failed to connect" not in status, \
+                 "Failed to connect to the daemon"
+
+      machineSae.wait_for_unit("hostapd.service")
+      machineSae.copy_from_vm("/run/hostapd/wlan0.hostapd.conf")
+      with subtest("Daemon can connect to the SAE access point using SAE"):
+          machineSae.wait_until_succeeds(
+            "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
+          )
+
+      with subtest("Daemon can connect to the SAE and WPA2 mixed access point using SAE"):
+          machineMixedUsingSae.wait_until_succeeds(
+            "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
+          )
+
+      with subtest("Daemon can connect to the SAE and WPA2 mixed access point using WPA2"):
+          machineMixedUsingWpa2.wait_until_succeeds(
+            "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
+          )
+
+      with subtest("Daemon can connect to the WPA2 access point using WPA2"):
+          machineWpa2.wait_until_succeeds(
+            "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
+          )
+    '';
+})
diff --git a/nixpkgs/nixos/tests/wrappers.nix b/nixpkgs/nixos/tests/wrappers.nix
new file mode 100644
index 000000000000..1d4fa85d7399
--- /dev/null
+++ b/nixpkgs/nixos/tests/wrappers.nix
@@ -0,0 +1,112 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  userUid = 1000;
+  usersGid = 100;
+  busybox = pkgs : pkgs.busybox.override {
+    # Without this, the busybox binary drops euid to ruid for most applets, including id.
+    # See https://bugs.busybox.net/show_bug.cgi?id=15101
+    extraConfig = "CONFIG_FEATURE_SUID n";
+  };
+in
+{
+  name = "wrappers";
+
+  nodes.machine = { config, pkgs, ... }: {
+    ids.gids.users = usersGid;
+
+    users.users = {
+      regular = {
+        uid = userUid;
+        isNormalUser = true;
+      };
+    };
+
+    security.apparmor.enable = true;
+
+    security.wrappers = {
+      suidRoot = {
+        owner = "root";
+        group = "root";
+        setuid = true;
+        source = "${busybox pkgs}/bin/busybox";
+        program = "suid_root_busybox";
+      };
+      sgidRoot = {
+        owner = "root";
+        group = "root";
+        setgid = true;
+        source = "${busybox pkgs}/bin/busybox";
+        program = "sgid_root_busybox";
+      };
+      withChown = {
+        owner = "root";
+        group = "root";
+        source = "${pkgs.libcap}/bin/capsh";
+        program = "capsh_with_chown";
+        capabilities = "cap_chown+ep";
+      };
+    };
+  };
+
+  testScript =
+    ''
+      def cmd_as_regular(cmd):
+        return "su -l regular -c '{0}'".format(cmd)
+
+      def test_as_regular(cmd, expected):
+        out = machine.succeed(cmd_as_regular(cmd)).strip()
+        assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out)
+
+      def test_as_regular_in_userns_mapped_as_root(cmd, expected):
+        out = machine.succeed(f"su -l regular -c '${pkgs.util-linux}/bin/unshare -rm {cmd}'").strip()
+        assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out)
+
+      test_as_regular('${busybox pkgs}/bin/busybox id -u', '${toString userUid}')
+      test_as_regular('${busybox pkgs}/bin/busybox id -ru', '${toString userUid}')
+      test_as_regular('${busybox pkgs}/bin/busybox id -g', '${toString usersGid}')
+      test_as_regular('${busybox pkgs}/bin/busybox id -rg', '${toString usersGid}')
+
+      test_as_regular('/run/wrappers/bin/suid_root_busybox id -u', '0')
+      test_as_regular('/run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('/run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}')
+      test_as_regular('/run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}')
+
+      test_as_regular('/run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}')
+      test_as_regular('/run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('/run/wrappers/bin/sgid_root_busybox id -g', '0')
+      test_as_regular('/run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}')
+
+      test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -u', '0')
+      test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -ru', '0')
+      test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -g', '0')
+      test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -rg', '0')
+
+      test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -u', '0')
+      test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -ru', '0')
+      test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0')
+      test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0')
+
+      # Test that in nonewprivs environment the wrappers simply exec their target.
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -u', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}')
+
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -g', '${toString usersGid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}')
+
+      # We are only testing the permitted set, because it's easiest to look at with capsh.
+      machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN'))
+      machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN'))
+      machine.succeed(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_CHOWN'))
+      machine.fail(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_SYS_ADMIN'))
+
+      # Test that the only user of apparmor policy includes generated by
+      # wrappers works. Ideally this'd be located in a test for the module that
+      # actually makes the apparmor policy for ping, but there's no convenient
+      # test for that one.
+      machine.succeed("ping -c 1 127.0.0.1")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/xandikos.nix b/nixpkgs/nixos/tests/xandikos.nix
new file mode 100644
index 000000000000..69d78ee21e76
--- /dev/null
+++ b/nixpkgs/nixos/tests/xandikos.nix
@@ -0,0 +1,70 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+
+    {
+      name = "xandikos";
+
+      meta.maintainers = with lib.maintainers; [ _0x4A6F ];
+
+      nodes = {
+        xandikos_client = {};
+        xandikos_default = {
+          networking.firewall.allowedTCPPorts = [ 8080 ];
+          services.xandikos.enable = true;
+        };
+        xandikos_proxy = {
+          networking.firewall.allowedTCPPorts = [ 80 8080 ];
+          services.xandikos.enable = true;
+          services.xandikos.address = "localhost";
+          services.xandikos.port = 8080;
+          services.xandikos.routePrefix = "/xandikos-prefix/";
+          services.xandikos.extraOptions = [
+            "--defaults"
+          ];
+          services.nginx = {
+            enable = true;
+            recommendedProxySettings = true;
+            virtualHosts."xandikos" = {
+              serverName = "xandikos.local";
+              basicAuth.xandikos = "snakeOilPassword";
+              locations."/xandikos/" = {
+                proxyPass = "http://localhost:8080/xandikos-prefix/";
+              };
+            };
+          };
+        };
+      };
+
+      testScript = ''
+        start_all()
+
+        with subtest("Xandikos default"):
+            xandikos_default.wait_for_unit("multi-user.target")
+            xandikos_default.wait_for_unit("xandikos.service")
+            xandikos_default.wait_for_open_port(8080)
+            xandikos_default.succeed("curl --fail http://localhost:8080/")
+            xandikos_default.succeed(
+                "curl -s --fail --location http://localhost:8080/ | grep -i Xandikos"
+            )
+            xandikos_client.wait_for_unit("network.target")
+            xandikos_client.fail("curl --fail http://xandikos_default:8080/")
+
+        with subtest("Xandikos proxy"):
+            xandikos_proxy.wait_for_unit("multi-user.target")
+            xandikos_proxy.wait_for_unit("xandikos.service")
+            xandikos_proxy.wait_for_open_port(8080)
+            xandikos_proxy.succeed("curl --fail http://localhost:8080/")
+            xandikos_proxy.succeed(
+                "curl -s --fail --location http://localhost:8080/ | grep -i Xandikos"
+            )
+            xandikos_client.wait_for_unit("network.target")
+            xandikos_client.fail("curl --fail http://xandikos_proxy:8080/")
+            xandikos_client.succeed(
+                "curl -s --fail -u xandikos:snakeOilPassword -H 'Host: xandikos.local' http://xandikos_proxy/xandikos/ | grep -i Xandikos"
+            )
+            xandikos_client.succeed(
+                "curl -s --fail -u xandikos:snakeOilPassword -H 'Host: xandikos.local' http://xandikos_proxy/xandikos/user/ | grep -i Xandikos"
+            )
+      '';
+    }
+)
diff --git a/nixpkgs/nixos/tests/xautolock.nix b/nixpkgs/nixos/tests/xautolock.nix
new file mode 100644
index 000000000000..cf81c4a1cf05
--- /dev/null
+++ b/nixpkgs/nixos/tests/xautolock.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+{
+  name = "xautolock";
+  meta.maintainers = [ ];
+
+  nodes.machine = {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+
+    test-support.displayManager.auto.user = "bob";
+    services.xserver.xautolock.enable = true;
+    services.xserver.xautolock.time = 1;
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_x()
+    machine.fail("pgrep xlock")
+    machine.sleep(120)
+    machine.succeed("pgrep xlock")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xfce.nix b/nixpkgs/nixos/tests/xfce.nix
new file mode 100644
index 000000000000..9620e9188cbf
--- /dev/null
+++ b/nixpkgs/nixos/tests/xfce.nix
@@ -0,0 +1,75 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "xfce";
+
+  nodes.machine =
+    { pkgs, ... }:
+
+    {
+      imports = [
+        ./common/user-account.nix
+      ];
+
+      services.xserver.enable = true;
+
+      services.xserver.displayManager = {
+        lightdm.enable = true;
+        autoLogin = {
+          enable = true;
+          user = "alice";
+        };
+      };
+
+      services.xserver.desktopManager.xfce.enable = true;
+      environment.systemPackages = [ pkgs.xfce.xfce4-whiskermenu-plugin ];
+
+      hardware.pulseaudio.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
+
+    };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
+  in ''
+      with subtest("Wait for login"):
+        machine.wait_for_x()
+        machine.wait_for_file("${user.home}/.Xauthority")
+        machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+        machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if Xfce components actually start"):
+        machine.wait_for_window("xfce4-panel")
+        machine.wait_for_window("Desktop")
+        for i in ["xfwm4", "xfsettingsd", "xfdesktop", "xfce4-screensaver", "xfce4-notifyd", "xfconfd"]:
+          machine.wait_until_succeeds(f"pgrep -f {i}")
+
+      with subtest("Open whiskermenu"):
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfconf-query -c xfce4-panel -p /plugins/plugin-1 -t string -s whiskermenu -n >&2 &'")
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfconf-query -c xfce4-panel -p /plugins/plugin-1/stay-on-focus-out -t bool -s true -n >&2 &'")
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfce4-panel -r >&2 &'")
+        machine.wait_until_succeeds("journalctl -b --grep 'xfce4-panel: Restarting' -t xsession")
+        machine.sleep(5)
+        machine.wait_until_succeeds("pgrep -f libwhiskermenu")
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfce4-popup-whiskermenu >&2 &'")
+        machine.wait_for_text('Mail Reader')
+        # Close the menu.
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfce4-popup-whiskermenu >&2 &'")
+
+      with subtest("Open Xfce terminal"):
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 xfce4-terminal >&2 &'")
+        machine.wait_for_window("Terminal")
+
+      with subtest("Open Thunar"):
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 thunar >&2 &'")
+        machine.wait_for_window("Thunar")
+        machine.wait_for_text('(Pictures|Public|Templates|Videos)')
+
+      with subtest("Check if any coredumps are found"):
+        machine.succeed("(coredumpctl --json=short 2>&1 || true) | grep 'No coredumps found'")
+        machine.sleep(10)
+        machine.screenshot("screen")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/xmonad-xdg-autostart.nix b/nixpkgs/nixos/tests/xmonad-xdg-autostart.nix
new file mode 100644
index 000000000000..2577a9ce2ea1
--- /dev/null
+++ b/nixpkgs/nixos/tests/xmonad-xdg-autostart.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "xmonad-xdg-autostart";
+  meta.maintainers = with lib.maintainers; [ oxalica ];
+
+  nodes.machine = { pkgs, config, ... }: {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    test-support.displayManager.auto.user = "alice";
+    services.xserver.displayManager.defaultSession = "none+xmonad";
+    services.xserver.windowManager.xmonad.enable = true;
+    services.xserver.desktopManager.runXdgAutostartIfNone = true;
+
+    environment.systemPackages = [
+      (pkgs.writeTextFile {
+        name = "test-xdg-autostart";
+        destination = "/etc/xdg/autostart/test-xdg-autostart.desktop";
+        text = ''
+          [Desktop Entry]
+          Name=test-xdg-autoatart
+          Type=Application
+          Terminal=false
+          Exec=${pkgs.coreutils}/bin/touch ${config.users.users.alice.home}/xdg-autostart-executed
+        '';
+      })
+    ];
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.config.users.users.alice;
+    in
+    ''
+      machine.wait_for_x()
+      machine.wait_for_file("${user.home}/xdg-autostart-executed")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/xmonad.nix b/nixpkgs/nixos/tests/xmonad.nix
new file mode 100644
index 000000000000..ec48c3e11275
--- /dev/null
+++ b/nixpkgs/nixos/tests/xmonad.nix
@@ -0,0 +1,117 @@
+import ./make-test-python.nix ({ pkgs, ...}:
+
+let
+  mkConfig = name: keys: ''
+    import XMonad
+    import XMonad.Operations (restart)
+    import XMonad.Util.EZConfig
+    import XMonad.Util.SessionStart
+    import Control.Monad (when)
+    import Text.Printf (printf)
+    import System.Posix.Process (executeFile)
+    import System.Info (arch,os)
+    import System.Environment (getArgs)
+    import System.FilePath ((</>))
+
+    main = do
+      dirs <- getDirectories
+      launch (def { startupHook = startup } `additionalKeysP` myKeys) dirs
+
+    startup = isSessionStart >>= \sessInit ->
+      spawn "touch /tmp/${name}"
+        >> if sessInit then setSessionStarted else spawn "xterm"
+
+    myKeys = [${builtins.concatStringsSep ", " keys}]
+
+    compiledConfig = printf "xmonad-%s-%s" arch os
+
+    compileRestart resume = do
+      dirs <- asks directories
+
+      whenX (recompile dirs True) $
+        when resume writeStateToFile
+          *> catchIO
+            ( do
+                args <- getArgs
+                executeFile (cacheDir dirs </> compiledConfig) False args Nothing
+            )
+  '';
+
+  oldKeys =
+    [ ''("M-C-x", spawn "xterm")''
+      ''("M-q", restart "xmonad" True)''
+      ''("M-C-q", compileRestart True)''
+      ''("M-C-t", spawn "touch /tmp/somefile")'' # create somefile
+    ];
+
+  newKeys =
+    [ ''("M-C-x", spawn "xterm")''
+      ''("M-q", restart "xmonad" True)''
+      ''("M-C-q", compileRestart True)''
+      ''("M-C-r", spawn "rm /tmp/somefile")'' # delete somefile
+    ];
+
+  newConfig = pkgs.writeText "xmonad.hs" (mkConfig "newXMonad" newKeys);
+in {
+  name = "xmonad";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ nequissimus ivanbrennan ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+    test-support.displayManager.auto.user = "alice";
+    services.xserver.displayManager.defaultSession = "none+xmonad";
+    services.xserver.windowManager.xmonad = {
+      enable = true;
+      enableConfiguredRecompile = true;
+      enableContribAndExtras = true;
+      extraPackages = with pkgs.haskellPackages; haskellPackages: [ xmobar ];
+      config = mkConfig "oldXMonad" oldKeys;
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.config.users.users.alice;
+  in ''
+    machine.wait_for_x()
+    machine.wait_for_file("${user.home}/.Xauthority")
+    machine.succeed("xauth merge ${user.home}/.Xauthority")
+    machine.send_key("alt-ctrl-x")
+    machine.wait_for_window("${user.name}.*machine")
+    machine.sleep(1)
+    machine.screenshot("terminal1")
+    machine.succeed("rm /tmp/oldXMonad")
+    machine.send_key("alt-q")
+    machine.wait_for_file("/tmp/oldXMonad")
+    machine.wait_for_window("${user.name}.*machine")
+    machine.sleep(1)
+    machine.screenshot("terminal2")
+
+    # /tmp/somefile should not exist yet
+    machine.fail("stat /tmp/somefile")
+
+    # original config has a keybinding that creates somefile
+    machine.send_key("alt-ctrl-t")
+    machine.wait_for_file("/tmp/somefile")
+
+    # set up the new config
+    machine.succeed("mkdir -p ${user.home}/.xmonad")
+    machine.copy_from_host("${newConfig}", "${user.home}/.config/xmonad/xmonad.hs")
+
+    # recompile xmonad using the new config
+    machine.send_key("alt-ctrl-q")
+    machine.wait_for_file("/tmp/newXMonad")
+
+    # new config has a keybinding that deletes somefile
+    machine.send_key("alt-ctrl-r")
+    machine.wait_until_fails("stat /tmp/somefile", timeout=30)
+
+    # restart with the old config, and confirm the old keybinding is back
+    machine.succeed("rm /tmp/oldXMonad")
+    machine.send_key("alt-q")
+    machine.wait_for_file("/tmp/oldXMonad")
+    machine.send_key("alt-ctrl-t")
+    machine.wait_for_file("/tmp/somefile")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xmpp/ejabberd.nix b/nixpkgs/nixos/tests/xmpp/ejabberd.nix
new file mode 100644
index 000000000000..1a807b27b6f6
--- /dev/null
+++ b/nixpkgs/nixos/tests/xmpp/ejabberd.nix
@@ -0,0 +1,278 @@
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "ejabberd";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+  nodes = {
+    client = { nodes, pkgs, ... }: {
+      networking.extraHosts = ''
+        ${nodes.server.config.networking.primaryIPAddress} example.com
+      '';
+
+      environment.systemPackages = [
+        (pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = nodes.server.config.networking.primaryIPAddress; })
+      ];
+    };
+    server = { config, pkgs, ... }: {
+      networking.extraHosts = ''
+        ${config.networking.primaryIPAddress} example.com
+      '';
+
+      services.ejabberd = {
+        enable = true;
+        configFile = "/etc/ejabberd.yml";
+      };
+
+      environment.etc."ejabberd.yml" = {
+        user = "ejabberd";
+        mode = "0600";
+        text = ''
+          loglevel: 3
+
+          hosts:
+            - "example.com"
+
+          listen:
+            -
+              port: 5222
+              module: ejabberd_c2s
+              zlib: false
+              max_stanza_size: 65536
+              shaper: c2s_shaper
+              access: c2s
+            -
+              port: 5269
+              ip: "::"
+              module: ejabberd_s2s_in
+            -
+              port: 5347
+              ip: "127.0.0.1"
+              module: ejabberd_service
+              access: local
+              shaper: fast
+            -
+              port: 5444
+              module: ejabberd_http
+              request_handlers:
+                "/upload": mod_http_upload
+
+          ## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text
+          ## password storage (see auth_password_format option).
+          disable_sasl_mechanisms: "digest-md5"
+
+          ## Outgoing S2S options
+          ## Preferred address families (which to try first) and connect timeout
+          ## in seconds.
+          outgoing_s2s_families:
+             - ipv4
+             - ipv6
+
+          ## auth_method: Method used to authenticate the users.
+          ## The default method is the internal.
+          ## If you want to use a different method,
+          ## comment this line and enable the correct ones.
+          auth_method: internal
+
+          ## Store the plain passwords or hashed for SCRAM:
+          ## auth_password_format: plain
+          auth_password_format: scram
+
+          ###'  TRAFFIC SHAPERS
+          shaper:
+            # in B/s
+            normal: 1000000
+            fast: 50000000
+
+          ## This option specifies the maximum number of elements in the queue
+          ## of the FSM. Refer to the documentation for details.
+          max_fsm_queue: 1000
+
+          ###'   ACCESS CONTROL LISTS
+          acl:
+            ## The 'admin' ACL grants administrative privileges to XMPP accounts.
+            ## You can put here as many accounts as you want.
+            admin:
+               user:
+                 - "root": "example.com"
+
+            ## Local users: don't modify this.
+            local:
+              user_regexp: ""
+
+            ## Loopback network
+            loopback:
+              ip:
+                - "127.0.0.0/8"
+                - "::1/128"
+                - "::FFFF:127.0.0.1/128"
+
+          ###'  SHAPER RULES
+          shaper_rules:
+            ## Maximum number of simultaneous sessions allowed for a single user:
+            max_user_sessions: 10
+            ## Maximum number of offline messages that users can have:
+            max_user_offline_messages:
+              - 5000: admin
+              - 1024
+            ## For C2S connections, all users except admins use the "normal" shaper
+            c2s_shaper:
+              - none: admin
+              - normal
+            ## All S2S connections use the "fast" shaper
+            s2s_shaper: fast
+
+          ###'  ACCESS RULES
+          access_rules:
+            ## This rule allows access only for local users:
+            local:
+              - allow: local
+            ## Only non-blocked users can use c2s connections:
+            c2s:
+              - deny: blocked
+              - allow
+            ## Only admins can send announcement messages:
+            announce:
+              - allow: admin
+            ## Only admins can use the configuration interface:
+            configure:
+              - allow: admin
+            ## Only accounts of the local ejabberd server can create rooms:
+            muc_create:
+              - allow: local
+            ## Only accounts on the local ejabberd server can create Pubsub nodes:
+            pubsub_createnode:
+              - allow: local
+            ## In-band registration allows registration of any possible username.
+            ## To disable in-band registration, replace 'allow' with 'deny'.
+            register:
+              - allow
+            ## Only allow to register from localhost
+            trusted_network:
+              - allow: loopback
+
+          ## ===============
+          ## API PERMISSIONS
+          ## ===============
+          ##
+          ## This section allows you to define who and using what method
+          ## can execute commands offered by ejabberd.
+          ##
+          ## By default "console commands" section allow executing all commands
+          ## issued using ejabberdctl command, and "admin access" section allows
+          ## users in admin acl that connect from 127.0.0.1 to  execute all
+          ## commands except start and stop with any available access method
+          ## (ejabberdctl, http-api, xmlrpc depending what is enabled on server).
+          ##
+          ## If you remove "console commands" there will be one added by
+          ## default allowing executing all commands, but if you just change
+          ## permissions in it, version from config file will be used instead
+          ## of default one.
+          ##
+          api_permissions:
+            "console commands":
+              from:
+                - ejabberd_ctl
+              who: all
+              what: "*"
+
+          language: "en"
+
+          ###'  MODULES
+          ## Modules enabled in all ejabberd virtual hosts.
+          modules:
+            mod_adhoc: {}
+            mod_announce: # recommends mod_adhoc
+              access: announce
+            mod_blocking: {} # requires mod_privacy
+            mod_caps: {}
+            mod_carboncopy: {}
+            mod_client_state: {}
+            mod_configure: {} # requires mod_adhoc
+            ## mod_delegation: {} # for xep0356
+            mod_disco: {}
+            #mod_irc:
+            #  host: "irc.@HOST@"
+            #  default_encoding: "utf-8"
+            ## mod_bosh: {}
+            ## mod_http_fileserver:
+            ##   docroot: "/var/www"
+            ##   accesslog: "/var/log/ejabberd/access.log"
+            mod_http_upload:
+              thumbnail: false # otherwise needs the identify command from ImageMagick installed
+              put_url: "http://@HOST@:5444/upload"
+            ##   # docroot: "@HOME@/upload"
+            #mod_http_upload_quota:
+            #  max_days: 14
+            mod_last: {}
+            ## XEP-0313: Message Archive Management
+            ## You might want to setup a SQL backend for MAM because the mnesia database is
+            ## limited to 2GB which might be exceeded on large servers
+            mod_mam: {}
+            mod_muc:
+              host: "muc.@HOST@"
+              access:
+                - allow
+              access_admin:
+                - allow: admin
+              access_create: muc_create
+              access_persistent: muc_create
+            mod_muc_admin: {}
+            mod_muc_log: {}
+            mod_offline:
+              access_max_user_messages: max_user_offline_messages
+            mod_ping: {}
+            ## mod_pres_counter:
+            ##   count: 5
+            ##   interval: 60
+            mod_privacy: {}
+            mod_private: {}
+            mod_roster:
+                versioning: true
+            mod_shared_roster: {}
+            mod_stats: {}
+            mod_time: {}
+            mod_vcard:
+              search: false
+            mod_vcard_xupdate: {}
+            ## Convert all avatars posted by Android clients from WebP to JPEG
+            mod_avatar: {}
+            #  convert:
+            #    webp: jpeg
+            mod_version: {}
+            mod_stream_mgmt: {}
+            ##   The module for S2S dialback (XEP-0220). Please note that you cannot
+            ##   rely solely on dialback if you want to federate with other servers,
+            ##   because a lot of servers have dialback disabled and instead rely on
+            ##   PKIX authentication. Make sure you have proper certificates installed
+            ##   and check your accessibility at https://check.messaging.one/
+            mod_s2s_dialback: {}
+            mod_pubsub:
+              plugins:
+                - "pep"
+            mod_push: {}
+        '';
+      };
+
+      networking.firewall.enable = false;
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    ejabberd_prefix = "su ejabberd -s $(which ejabberdctl) "
+
+    server.wait_for_unit("ejabberd.service")
+
+    assert "status: started" in server.succeed(ejabberd_prefix + "status")
+
+    server.succeed(
+        ejabberd_prefix + "register azurediamond example.com hunter2",
+        ejabberd_prefix + "register cthon98 example.com nothunter2",
+    )
+    server.fail(ejabberd_prefix + "register asdf wrong.domain")
+    client.succeed("send-message")
+    server.succeed(
+        ejabberd_prefix + "unregister cthon98 example.com",
+        ejabberd_prefix + "unregister azurediamond example.com",
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xmpp/prosody-mysql.nix b/nixpkgs/nixos/tests/xmpp/prosody-mysql.nix
new file mode 100644
index 000000000000..40f3e308a04e
--- /dev/null
+++ b/nixpkgs/nixos/tests/xmpp/prosody-mysql.nix
@@ -0,0 +1,124 @@
+let
+  cert = pkgs: pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=example.com/CN=uploads.example.com/CN=conference.example.com' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+  createUsers = pkgs: pkgs.writeScriptBin "create-prosody-users" ''
+    #!${pkgs.bash}/bin/bash
+    set -e
+
+    # Creates and set password for the 2 xmpp test users.
+    #
+    # Doing that in a bash script instead of doing that in the test
+    # script allow us to easily provision the users when running that
+    # test interactively.
+
+    prosodyctl register cthon98 example.com nothunter2
+    prosodyctl register azurediamond example.com hunter2
+  '';
+  delUsers = pkgs: pkgs.writeScriptBin "delete-prosody-users" ''
+    #!${pkgs.bash}/bin/bash
+    set -e
+
+    # Deletes the test users.
+    #
+    # Doing that in a bash script instead of doing that in the test
+    # script allow us to easily provision the users when running that
+    # test interactively.
+
+    prosodyctl deluser cthon98@example.com
+    prosodyctl deluser azurediamond@example.com
+  '';
+in import ../make-test-python.nix {
+  name = "prosody-mysql";
+  nodes = {
+    client = { nodes, pkgs, config, ... }: {
+      security.pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      console.keyMap = "fr-bepo";
+      networking.extraHosts = ''
+        ${nodes.server.config.networking.primaryIPAddress} example.com
+        ${nodes.server.config.networking.primaryIPAddress} conference.example.com
+        ${nodes.server.config.networking.primaryIPAddress} uploads.example.com
+      '';
+      environment.systemPackages = [
+        (pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = nodes.server.config.networking.primaryIPAddress; })
+      ];
+    };
+    server = { config, pkgs, ... }: {
+      nixpkgs.overlays = [
+        (self: super: {
+          prosody = super.prosody.override {
+            withExtraLuaPackages = p: [ p.luadbi-mysql ];
+          };
+        })
+      ];
+      security.pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      console.keyMap = "fr-bepo";
+      networking.extraHosts = ''
+        ${config.networking.primaryIPAddress} example.com
+        ${config.networking.primaryIPAddress} conference.example.com
+        ${config.networking.primaryIPAddress} uploads.example.com
+      '';
+      networking.firewall.enable = false;
+      environment.systemPackages = [
+        (createUsers pkgs)
+        (delUsers pkgs)
+      ];
+      services.prosody = {
+        enable = true;
+        ssl.cert = "${cert pkgs}/cert.pem";
+        ssl.key = "${cert pkgs}/key.pem";
+        virtualHosts.example = {
+          domain = "example.com";
+          enabled = true;
+          ssl.cert = "${cert pkgs}/cert.pem";
+          ssl.key = "${cert pkgs}/key.pem";
+        };
+        muc = [
+          {
+            domain = "conference.example.com";
+          }
+        ];
+        uploadHttp = {
+          domain = "uploads.example.com";
+        };
+        extraConfig = ''
+          storage = "sql"
+          sql = {
+            driver = "MySQL";
+            database = "prosody";
+            host = "mysql";
+            port = 3306;
+            username = "prosody";
+            password = "password123";
+          };
+        '';
+      };
+    };
+    mysql = { config, pkgs, ... }: {
+      networking.firewall.enable = false;
+      services.mysql = {
+        enable = true;
+        initialScript = pkgs.writeText "mysql_init.sql" ''
+          CREATE DATABASE prosody;
+          CREATE USER 'prosody'@'server' IDENTIFIED BY 'password123';
+          GRANT ALL PRIVILEGES ON prosody.* TO 'prosody'@'server';
+          FLUSH PRIVILEGES;
+        '';
+        package = pkgs.mariadb;
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    # Check with mysql storage
+    mysql.wait_for_unit("mysql.service")
+    server.wait_for_unit("prosody.service")
+    server.succeed('prosodyctl status | grep "Prosody is running"')
+
+    server.succeed("create-prosody-users")
+    client.succeed("send-message")
+    server.succeed("delete-prosody-users")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/xmpp/prosody.nix b/nixpkgs/nixos/tests/xmpp/prosody.nix
new file mode 100644
index 000000000000..045ae6430fd4
--- /dev/null
+++ b/nixpkgs/nixos/tests/xmpp/prosody.nix
@@ -0,0 +1,93 @@
+let
+  cert = pkgs: pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=example.com/CN=uploads.example.com/CN=conference.example.com' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+  createUsers = pkgs: pkgs.writeScriptBin "create-prosody-users" ''
+    #!${pkgs.bash}/bin/bash
+    set -e
+
+    # Creates and set password for the 2 xmpp test users.
+    #
+    # Doing that in a bash script instead of doing that in the test
+    # script allow us to easily provision the users when running that
+    # test interactively.
+
+    prosodyctl register cthon98 example.com nothunter2
+    prosodyctl register azurediamond example.com hunter2
+  '';
+  delUsers = pkgs: pkgs.writeScriptBin "delete-prosody-users" ''
+    #!${pkgs.bash}/bin/bash
+    set -e
+
+    # Deletes the test users.
+    #
+    # Doing that in a bash script instead of doing that in the test
+    # script allow us to easily provision the users when running that
+    # test interactively.
+
+    prosodyctl deluser cthon98@example.com
+    prosodyctl deluser azurediamond@example.com
+  '';
+in import ../make-test-python.nix {
+  name = "prosody";
+  nodes = {
+    client = { nodes, pkgs, config, ... }: {
+      security.pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      console.keyMap = "fr-bepo";
+      networking.extraHosts = ''
+        ${nodes.server.config.networking.primaryIPAddress} example.com
+        ${nodes.server.config.networking.primaryIPAddress} conference.example.com
+        ${nodes.server.config.networking.primaryIPAddress} uploads.example.com
+      '';
+      environment.systemPackages = [
+        (pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = "example.com"; })
+      ];
+    };
+    server = { config, pkgs, ... }: {
+      security.pki.certificateFiles = [ "${cert pkgs}/cert.pem" ];
+      console.keyMap = "fr-bepo";
+      networking.extraHosts = ''
+        ${config.networking.primaryIPAddress} example.com
+        ${config.networking.primaryIPAddress} conference.example.com
+        ${config.networking.primaryIPAddress} uploads.example.com
+      '';
+      networking.firewall.enable = false;
+      environment.systemPackages = [
+        (createUsers pkgs)
+        (delUsers pkgs)
+      ];
+      services.prosody = {
+        enable = true;
+        ssl.cert = "${cert pkgs}/cert.pem";
+        ssl.key = "${cert pkgs}/key.pem";
+        virtualHosts.example = {
+          domain = "example.com";
+          enabled = true;
+          ssl.cert = "${cert pkgs}/cert.pem";
+          ssl.key = "${cert pkgs}/key.pem";
+        };
+        muc = [
+          {
+            domain = "conference.example.com";
+          }
+        ];
+        uploadHttp = {
+          domain = "uploads.example.com";
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    # Check with sqlite storage
+    start_all()
+    server.wait_for_unit("prosody.service")
+    server.succeed('prosodyctl status | grep "Prosody is running"')
+
+    server.succeed("create-prosody-users")
+    client.succeed("send-message")
+    server.succeed("delete-prosody-users")
+  '';
+}
diff --git a/nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix b/nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix
new file mode 100644
index 000000000000..8ccac0612491
--- /dev/null
+++ b/nixpkgs/nixos/tests/xmpp/xmpp-sendmessage.nix
@@ -0,0 +1,90 @@
+{ writeScriptBin, writeText, python3, connectTo ? "localhost" }:
+let
+  dummyFile = writeText "dummy-file" ''
+    Dear dog,
+
+    Please find this *really* important attachment.
+
+    Yours truly,
+    Bob
+  '';
+in writeScriptBin "send-message" ''
+#!${(python3.withPackages (ps: [ ps.slixmpp ])).interpreter}
+import logging
+import sys
+import signal
+from types import MethodType
+
+from slixmpp import ClientXMPP
+from slixmpp.exceptions import IqError, IqTimeout
+
+
+class CthonTest(ClientXMPP):
+
+    def __init__(self, jid, password):
+        ClientXMPP.__init__(self, jid, password)
+        self.add_event_handler("session_start", self.session_start)
+        self.test_succeeded = False
+
+    async def session_start(self, event):
+        try:
+            # Exceptions in event handlers are printed to stderr but not
+            # propagated, they do not make the script terminate with a non-zero
+            # exit code. We use the `test_succeeded` flag as a workaround and
+            # check it later at the end of the script to exit with a proper
+            # exit code.
+            # Additionally, this flag ensures that this event handler has been
+            # actually run by ClientXMPP, which may well not be the case.
+            await self.test_xmpp_server()
+            self.test_succeeded = True
+        finally:
+            # Even if an exception happens in `test_xmpp_server()`, we still
+            # need to disconnect explicitly, otherwise the process will hang
+            # forever.
+            self.disconnect(wait=True)
+
+    async def test_xmpp_server(self):
+        log = logging.getLogger(__name__)
+        self.send_presence()
+        self.get_roster()
+        # Sending a test message
+        self.send_message(mto="azurediamond@example.com", mbody="Hello, this is dog.", mtype="chat")
+        log.info('Message sent')
+
+        # Test http upload (XEP_0363)
+        try:
+            url = await self['xep_0363'].upload_file("${dummyFile}",timeout=10)
+        except:
+            log.error("ERROR: Cannot run upload command. XEP_0363 seems broken")
+            sys.exit(1)
+        log.info('Upload success!')
+
+        # Test MUC
+        # TODO: use join_muc_wait() after slixmpp 1.8.0 is released.
+        self.plugin['xep_0045'].join_muc('testMucRoom', 'cthon98')
+        log.info('MUC join success!')
+        log.info('XMPP SCRIPT TEST SUCCESS')
+
+def timeout_handler(signalnum, stackframe):
+    print('ERROR: xmpp-sendmessage timed out')
+    sys.exit(1)
+
+if __name__ == '__main__':
+    signal.signal(signal.SIGALRM, timeout_handler)
+    signal.alarm(120)
+    logging.basicConfig(level=logging.DEBUG,
+                        format='%(levelname)-8s %(message)s')
+
+    ct = CthonTest('cthon98@example.com', 'nothunter2')
+    ct.register_plugin('xep_0071')
+    ct.register_plugin('xep_0128')
+    # HTTP Upload
+    ct.register_plugin('xep_0363')
+    # MUC
+    ct.register_plugin('xep_0045')
+    ct.connect(("${connectTo}", 5222))
+    ct.process(forever=False)
+
+    if not ct.test_succeeded:
+        sys.exit(1)
+''
diff --git a/nixpkgs/nixos/tests/xpadneo.nix b/nixpkgs/nixos/tests/xpadneo.nix
new file mode 100644
index 000000000000..c7b72831fce8
--- /dev/null
+++ b/nixpkgs/nixos/tests/xpadneo.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "xpadneo";
+  meta.maintainers = with lib.maintainers; [ kira-bruneau ];
+
+  nodes = {
+    machine = {
+      config.hardware.xpadneo.enable = true;
+    };
+  };
+
+  # This is just a sanity check to make sure the module was
+  # loaded. We'd have to find some way to mock an xbox controller if
+  # we wanted more in-depth testing.
+  testScript = ''
+    machine.start();
+    machine.succeed("modinfo hid_xpadneo | grep 'version:\s\+${pkgs.linuxPackages.xpadneo.version}'")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xrdp-with-audio-pulseaudio.nix b/nixpkgs/nixos/tests/xrdp-with-audio-pulseaudio.nix
new file mode 100644
index 000000000000..27da7c457c49
--- /dev/null
+++ b/nixpkgs/nixos/tests/xrdp-with-audio-pulseaudio.nix
@@ -0,0 +1,97 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  # How to interactively test this module if the audio actually works
+
+  # - nix run .#pulseaudio-module-xrdp.tests.xrdp-with-audio-pulseaudio.driverInteractive
+  # - test_script() # launches the terminal and the tests itself
+  # - server.send_monitor_command("hostfwd_add tcp::3389-:3389") # forward the RDP port to the host
+  # - Connect with the RDP client you like (ex: Remmina)
+  # - Don't forget to enable audio support. In remmina: Advanced -> Audio output mode to Local (default is Off)
+  # - Open a browser or something that plays sound. Ex: chromium
+
+  name = "xrdp-with-audio-pulseaudio";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lucasew ];
+  };
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ ./common/user-account.nix ];
+
+      environment.etc."xrdp/test.txt".text = "Shouldn't conflict";
+
+      services.xrdp.enable = true;
+      services.xrdp.audio.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.xterm}/bin/xterm";
+
+      hardware.pulseaudio = {
+        enable = true;
+      };
+
+      systemd.user.services.pactl-list = {
+        script = ''
+          while [ ! -S /tmp/.xrdp/xrdp_chansrv_audio_in_socket_* ]; do
+            sleep 1
+          done
+          sleep 1
+          ${pkgs.pulseaudio}/bin/pactl list
+          echo Source:
+          ${pkgs.pulseaudio}/bin/pactl get-default-source | tee /tmp/pulseaudio-source
+          echo Sink:
+          ${pkgs.pulseaudio}/bin/pactl get-default-sink | tee /tmp/pulseaudio-sink
+
+        '';
+        wantedBy = [ "default.target" ];
+      };
+
+      networking.firewall.allowedTCPPorts = [ 3389 ];
+    };
+
+    client = { pkgs, ... }: {
+      imports = [ ./common/x11.nix ./common/user-account.nix ];
+      test-support.displayManager.auto.user = "alice";
+
+      environment.systemPackages = [ pkgs.freerdp ];
+
+      services.xrdp.enable = true;
+      services.xrdp.audio.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.icewm}/bin/icewm";
+
+      hardware.pulseaudio = {
+        enable = true;
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.client.config.users.users.alice;
+  in ''
+    start_all()
+
+    client.wait_for_x()
+    client.wait_for_file("${user.home}/.Xauthority")
+    client.succeed("xauth merge ${user.home}/.Xauthority")
+
+    client.sleep(5)
+
+    client.execute("xterm >&2 &")
+    client.sleep(1)
+
+    client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:127.0.0.1 /u:${user.name} /p:${user.password} /sound\n")
+
+    client.sleep(10)
+
+    client.succeed("[ -S /tmp/.xrdp/xrdp_chansrv_audio_in_socket_* ]") # checks if it's a socket
+    client.sleep(5)
+    client.screenshot("localrdp")
+
+    client.execute("xterm >&2 &")
+    client.sleep(1)
+    client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:server /u:${user.name} /p:${user.password} /sound\n")
+    client.sleep(10)
+
+    server.succeed("[ -S /tmp/.xrdp/xrdp_chansrv_audio_in_socket_* ]") # checks if it's a socket
+    server.succeed('[ "$(cat /tmp/pulseaudio-source)" == "xrdp-source" ]')
+    server.succeed('[ "$(cat /tmp/pulseaudio-sink)" == "xrdp-sink" ]')
+    client.screenshot("remoterdp")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xrdp.nix b/nixpkgs/nixos/tests/xrdp.nix
new file mode 100644
index 000000000000..f277d4b79525
--- /dev/null
+++ b/nixpkgs/nixos/tests/xrdp.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "xrdp";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ ./common/user-account.nix ];
+      services.xrdp.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.xterm}/bin/xterm";
+      networking.firewall.allowedTCPPorts = [ 3389 ];
+    };
+
+    client = { pkgs, ... }: {
+      imports = [ ./common/x11.nix ./common/user-account.nix ];
+      test-support.displayManager.auto.user = "alice";
+      environment.systemPackages = [ pkgs.freerdp ];
+      services.xrdp.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.icewm}/bin/icewm";
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.client.config.users.users.alice;
+  in ''
+    start_all()
+
+    client.wait_for_x()
+    client.wait_for_file("${user.home}/.Xauthority")
+    client.succeed("xauth merge ${user.home}/.Xauthority")
+
+    client.sleep(5)
+
+    client.execute("xterm >&2 &")
+    client.sleep(1)
+    client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:127.0.0.1 /u:${user.name} /p:${user.password}\n")
+    client.sleep(5)
+    client.screenshot("localrdp")
+
+    client.execute("xterm >&2 &")
+    client.sleep(1)
+    client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:server /u:${user.name} /p:${user.password}\n")
+    client.sleep(5)
+    client.screenshot("remoterdp")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xscreensaver.nix b/nixpkgs/nixos/tests/xscreensaver.nix
new file mode 100644
index 000000000000..820ddbb0e962
--- /dev/null
+++ b/nixpkgs/nixos/tests/xscreensaver.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "pass-secret-service";
+  meta.maintainers = with lib.maintainers; [ vancluever AndersonTorres ];
+
+  nodes = {
+    ok = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+      };
+
+    empty_wrapperPrefix = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+        nixpkgs.overlays = [
+          (self: super: {
+            xscreensaver = super.xscreensaver.override {
+              wrapperPrefix = "";
+            };
+          })
+        ];
+      };
+
+    bad_wrapperPrefix = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+        nixpkgs.overlays = [
+          (self: super: {
+            xscreensaver = super.xscreensaver.override {
+              wrapperPrefix = "/a/bad/path";
+            };
+          })
+        ];
+      };
+  };
+
+  testScript = ''
+    ok.wait_for_x()
+    ok.wait_for_unit("xscreensaver", "alice")
+    _, output_ok = ok.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' not in output_ok
+    assert 'your screen via the out-of-memory killer' not in output_ok
+    assert '"xscreensaver-auth" must be setuid root' not in output_ok
+
+    empty_wrapperPrefix.wait_for_x()
+    empty_wrapperPrefix.wait_for_unit("xscreensaver", "alice")
+    _, output_empty_wrapperPrefix = empty_wrapperPrefix.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' in output_empty_wrapperPrefix
+    assert 'your screen via the out-of-memory killer' in output_empty_wrapperPrefix
+    assert '"xscreensaver-auth" must be setuid root' in output_empty_wrapperPrefix
+
+    bad_wrapperPrefix.wait_for_x()
+    bad_wrapperPrefix.wait_for_unit("xscreensaver", "alice")
+    _, output_bad_wrapperPrefix = bad_wrapperPrefix.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' in output_bad_wrapperPrefix
+    assert 'your screen via the out-of-memory killer' in output_bad_wrapperPrefix
+    assert '"xscreensaver-auth" must be setuid root' in output_bad_wrapperPrefix
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xss-lock.nix b/nixpkgs/nixos/tests/xss-lock.nix
new file mode 100644
index 000000000000..e4e41a5aa797
--- /dev/null
+++ b/nixpkgs/nixos/tests/xss-lock.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "xss-lock";
+  meta.maintainers = [ ];
+
+  nodes = {
+    simple = {
+      imports = [ ./common/x11.nix ./common/user-account.nix ];
+      programs.xss-lock.enable = true;
+      test-support.displayManager.auto.user = "alice";
+    };
+
+    custom_lockcmd = { pkgs, ... }: {
+      imports = [ ./common/x11.nix ./common/user-account.nix ];
+      test-support.displayManager.auto.user = "alice";
+
+      programs.xss-lock = {
+        enable = true;
+        extraOptions = [ "-n" "${pkgs.libnotify}/bin/notify-send 'About to sleep!'"];
+        lockerCommand = "${pkgs.xlockmore}/bin/xlock -mode ant";
+      };
+    };
+  };
+
+  testScript = ''
+    def perform_xsslock_test(machine, lockCmd):
+        machine.start()
+        machine.wait_for_x()
+        machine.wait_for_unit("xss-lock.service", "alice")
+        machine.fail(f"pgrep {lockCmd}")
+        machine.succeed("su -l alice -c 'xset dpms force standby'")
+        machine.wait_until_succeeds(f"pgrep {lockCmd}")
+
+
+    with subtest("simple"):
+        perform_xsslock_test(simple, "i3lock")
+
+    with subtest("custom_cmd"):
+        perform_xsslock_test(custom_lockcmd, "xlock")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/xterm.nix b/nixpkgs/nixos/tests/xterm.nix
new file mode 100644
index 000000000000..745d33e8a0d5
--- /dev/null
+++ b/nixpkgs/nixos/tests/xterm.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "xterm";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ nequissimus ];
+  };
+
+  nodes.machine = { pkgs, ... }:
+    {
+      imports = [ ./common/x11.nix ];
+      services.xserver.desktopManager.xterm.enable = false;
+    };
+
+  testScript =
+    ''
+      machine.wait_for_x()
+      machine.succeed("DISPLAY=:0 xterm -title testterm -class testterm -fullscreen >&2 &")
+      machine.sleep(2)
+      machine.send_chars("echo $XTERM_VERSION >> /tmp/xterm_version\n")
+      machine.wait_for_file("/tmp/xterm_version")
+      assert "${pkgs.xterm.version}" in machine.succeed("cat /tmp/xterm_version")
+      machine.screenshot("window")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/xxh.nix b/nixpkgs/nixos/tests/xxh.nix
new file mode 100644
index 000000000000..3af8e53779e3
--- /dev/null
+++ b/nixpkgs/nixos/tests/xxh.nix
@@ -0,0 +1,67 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+  let
+    inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+    xxh-shell-zsh = pkgs.stdenv.mkDerivation {
+      pname = "xxh-shell-zsh";
+      version = "";
+      src = pkgs.fetchFromGitHub {
+        owner = "xxh";
+        repo = "xxh-shell-zsh";
+        # gets rarely updated, we can then just replace the hash
+        rev = "91e1f84f8d6e0852c3235d4813f341230cac439f";
+        sha256 = "sha256-Y1FrIRxTd0yooK+ZzKcCd6bLSy5E2fRXYAzrIsm7rIc=";
+      };
+
+      postPatch = ''
+        substituteInPlace build.sh \
+          --replace "echo Install wget or curl" "cp ${zsh-portable-binary} zsh-5.8-linux-x86_64.tar.gz" \
+          --replace "command -v curl" "command -v this-should-not-trigger"
+      '';
+
+      installPhase = ''
+        mkdir -p $out
+        mv * $out/
+      '';
+    };
+
+    zsh-portable-binary = pkgs.fetchurl {
+      # kept in sync with https://github.com/xxh/xxh-shell-zsh/tree/master/build.sh#L27
+      url = "https://github.com/romkatv/zsh-bin/releases/download/v3.0.1/zsh-5.8-linux-x86_64.tar.gz";
+      sha256 = "sha256-i8flMd2Isc0uLoeYQNDnOGb/kK3oTFVqQgIx7aOAIIo=";
+    };
+  in
+  {
+    name = "xxh";
+    meta = with lib.maintainers; {
+      maintainers = [ lom ];
+    };
+
+    nodes = {
+      server = { ... }: {
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+      };
+
+      client = { ... }: {
+        programs.zsh.enable = true;
+        users.users.root.shell = pkgs.zsh;
+        environment.systemPackages = with pkgs; [ xxh git ];
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      client.succeed("mkdir -m 700 /root/.ssh")
+
+      client.succeed(
+         "cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa"
+      )
+      client.succeed("chmod 600 /root/.ssh/id_ecdsa")
+
+      server.wait_for_unit("sshd")
+
+      client.succeed("xxh server -i /root/.ssh/id_ecdsa +hc \'echo $0\' +i +s zsh +I xxh-shell-zsh+path+${xxh-shell-zsh} | grep -Fq '/root/.xxh/.xxh/shells/xxh-shell-zsh/build/zsh-bin/bin/zsh'")
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/yabar.nix b/nixpkgs/nixos/tests/yabar.nix
new file mode 100644
index 000000000000..212a8ce4bbf5
--- /dev/null
+++ b/nixpkgs/nixos/tests/yabar.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "yabar";
+  meta.maintainers = [ ];
+
+  nodes.machine = {
+    imports = [ ./common/x11.nix ./common/user-account.nix ];
+
+    test-support.displayManager.auto.user = "bob";
+
+    programs.yabar.enable = true;
+    programs.yabar.bars = {
+      top.indicators.date.exec = "YABAR_DATE";
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_x()
+
+    # confirm proper startup
+    machine.wait_for_unit("yabar.service", "bob")
+    machine.sleep(10)
+    machine.wait_for_unit("yabar.service", "bob")
+
+    machine.screenshot("top_bar")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/yggdrasil.nix b/nixpkgs/nixos/tests/yggdrasil.nix
new file mode 100644
index 000000000000..70d148380bf7
--- /dev/null
+++ b/nixpkgs/nixos/tests/yggdrasil.nix
@@ -0,0 +1,172 @@
+let
+  aliceIp6 = "202:b70:9b0b:cf34:f93c:8f18:bbfd:7034";
+  aliceKeys = {
+    PublicKey = "3e91ec9e861960d86e1ce88051f97c435bdf2859640ab681dfa906eb45ad5182";
+    PrivateKey = "a867f9e078e4ce58d310cf5acd4622d759e2a21df07e1d6fc380a2a26489480d3e91ec9e861960d86e1ce88051f97c435bdf2859640ab681dfa906eb45ad5182";
+  };
+  bobIp6 = "202:a483:73a4:9f2d:a559:4a19:bc9:8458";
+  bobPrefix = "302:a483:73a4:9f2d";
+  bobConfig = {
+    InterfacePeers = {
+      eth1 = [ "tcp://192.168.1.200:12345" ];
+    };
+    MulticastInterfaces = [ {
+      Regex = ".*";
+      Beacon = true;
+      Listen = true;
+      Port = 54321;
+      Priority = 0;
+    } ];
+    PublicKey = "2b6f918b6c1a4b54d6bcde86cf74e074fb32ead4ee439b7930df2aa60c825186";
+    PrivateKey = "0c4a24acd3402722ce9277ed179f4a04b895b49586493c25fbaed60653d857d62b6f918b6c1a4b54d6bcde86cf74e074fb32ead4ee439b7930df2aa60c825186";
+  };
+  danIp6 = bobPrefix + "::2";
+
+in import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "yggdrasil";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ gazally ];
+  };
+
+  nodes = rec {
+    # Alice is listening for peerings on a specified port,
+    # but has multicast peering disabled.  Alice has part of her
+    # yggdrasil config in Nix and part of it in a file.
+    alice =
+      { ... }:
+      {
+        networking = {
+          interfaces.eth1.ipv4.addresses = [{
+            address = "192.168.1.200";
+            prefixLength = 24;
+          }];
+          firewall.allowedTCPPorts = [ 80 12345 ];
+        };
+        services.httpd.enable = true;
+        services.httpd.adminAddr = "foo@example.org";
+
+        services.yggdrasil = {
+          enable = true;
+          settings = {
+            Listen = ["tcp://0.0.0.0:12345"];
+            MulticastInterfaces = [ ];
+          };
+          configFile = toString (pkgs.writeTextFile {
+                         name = "yggdrasil-alice-conf";
+                         text = builtins.toJSON aliceKeys;
+                       });
+        };
+      };
+
+    # Bob is set up to peer with Alice, and also to do local multicast
+    # peering.  Bob's yggdrasil config is in a file.
+    bob =
+      { ... }:
+      {
+        networking.firewall.allowedTCPPorts = [ 54321 ];
+        services.yggdrasil = {
+          enable = true;
+          openMulticastPort = true;
+          configFile = toString (pkgs.writeTextFile {
+                         name = "yggdrasil-bob-conf";
+                         text = builtins.toJSON bobConfig;
+                       });
+        };
+
+        boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
+
+        networking = {
+          bridges.br0.interfaces = [ ];
+          interfaces.br0 = {
+            ipv6.addresses = [{
+              address = bobPrefix + "::1";
+              prefixLength = 64;
+            }];
+          };
+        };
+
+        # dan is a node inside a container running on bob's host.
+        containers.dan = {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          config = { config, pkgs, ... }: {
+            networking.interfaces.eth0.ipv6 = {
+              addresses = [{
+                address = bobPrefix + "::2";
+                prefixLength = 64;
+              }];
+              routes = [{
+                address = "200::";
+                prefixLength = 7;
+                via = bobPrefix + "::1";
+              }];
+            };
+            services.httpd.enable = true;
+            services.httpd.adminAddr = "foo@example.org";
+            networking.firewall.allowedTCPPorts = [ 80 ];
+          };
+        };
+      };
+
+    # Carol only does local peering.  Carol's yggdrasil config is all Nix.
+    carol =
+      { ... }:
+      {
+        networking.firewall.allowedTCPPorts = [ 43210 ];
+        services.yggdrasil = {
+          enable = true;
+          extraArgs = [ "-loglevel" "error" ];
+          denyDhcpcdInterfaces = [ "ygg0" ];
+          settings = {
+            IfTAPMode = true;
+            IfName = "ygg0";
+            MulticastInterfaces = [
+              {
+                Port = 43210;
+              }
+            ];
+            openMulticastPort = true;
+          };
+          persistentKeys = true;
+        };
+      };
+    };
+
+  testScript =
+    ''
+      import re
+
+      # Give Alice a head start so she is ready when Bob calls.
+      alice.start()
+      alice.wait_for_unit("yggdrasil.service")
+
+      bob.start()
+      carol.start()
+      bob.wait_for_unit("default.target")
+      carol.wait_for_unit("yggdrasil.service")
+
+      ip_addr_show = "ip -o -6 addr show dev ygg0 scope global"
+      carol.wait_until_succeeds(f"[ `{ip_addr_show} | grep -v tentative | wc -l` -ge 1 ]")
+      carol_ip6 = re.split(" +|/", carol.succeed(ip_addr_show))[3]
+
+      # If Alice can talk to Carol, then Bob's outbound peering and Carol's
+      # local peering have succeeded and everybody is connected.
+      alice.wait_until_succeeds(f"ping -c 1 {carol_ip6}")
+      alice.succeed("ping -c 1 ${bobIp6}")
+
+      bob.succeed("ping -c 1 ${aliceIp6}")
+      bob.succeed(f"ping -c 1 {carol_ip6}")
+
+      carol.succeed("ping -c 1 ${aliceIp6}")
+      carol.succeed("ping -c 1 ${bobIp6}")
+      carol.succeed("ping -c 1 ${bobPrefix}::1")
+      carol.succeed("ping -c 8 ${danIp6}")
+
+      carol.fail("journalctl -u dhcpcd | grep ygg0")
+
+      alice.wait_for_unit("httpd.service")
+      carol.succeed("curl --fail -g http://[${aliceIp6}]")
+      carol.succeed("curl --fail -g http://[${danIp6}]")
+    '';
+})
diff --git a/nixpkgs/nixos/tests/zammad.nix b/nixpkgs/nixos/tests/zammad.nix
new file mode 100644
index 000000000000..faae1949e37b
--- /dev/null
+++ b/nixpkgs/nixos/tests/zammad.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix (
+  { lib, pkgs, ... }:
+
+  {
+    name = "zammad";
+
+    meta.maintainers = with lib.maintainers; [ taeer n0emis netali ];
+
+    nodes.machine = { config, ... }: {
+      virtualisation = {
+        memorySize = 2048;
+      };
+
+      services.zammad.enable = true;
+      services.zammad.secretKeyBaseFile = pkgs.writeText "secret" ''
+        52882ef142066e09ab99ce816ba72522e789505caba224a52d750ec7dc872c2c371b2fd19f16b25dfbdd435a4dd46cb3df9f82eb63fafad715056bdfe25740d6
+      '';
+
+      systemd.services.zammad-locale-cheat =
+        let cfg = config.services.zammad; in
+        {
+          serviceConfig = {
+            Type = "simple";
+            Restart = "always";
+
+            User = "zammad";
+            Group = "zammad";
+            PrivateTmp = true;
+            StateDirectory = "zammad";
+            WorkingDirectory = cfg.dataDir;
+          };
+          wantedBy = [ "zammad-web.service" ];
+          description = "Hack in the locale files so zammad doesn't try to access the internet";
+          script = ''
+            mkdir -p ./config/translations
+            VERSION=$(cat ${cfg.package}/VERSION)
+
+            # If these files are not in place, zammad will try to access the internet.
+            # For the test, we only need to supply en-us.
+            echo '[{"locale":"en-us","alias":"en","name":"English (United States)","active":true,"dir":"ltr"}]' \
+              > ./config/locales-$VERSION.yml
+            echo '[{"locale":"en-us","format":"time","source":"date","target":"mm/dd/yyyy","target_initial":"mm/dd/yyyy"},{"locale":"en-us","format":"time","source":"timestamp","target":"mm/dd/yyyy HH:MM","target_initial":"mm/dd/yyyy HH:MM"}]' \
+              > ./config/translations/en-us-$VERSION.yml
+          '';
+        };
+    };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("postgresql.service")
+      machine.wait_for_unit("redis-zammad.service")
+      machine.wait_for_unit("zammad-web.service")
+      machine.wait_for_unit("zammad-websocket.service")
+      machine.wait_for_unit("zammad-worker.service")
+      # wait for zammad to fully come up
+      machine.sleep(120)
+
+      # without the grep the command does not produce valid utf-8 for some reason
+      with subtest("welcome screen loads"):
+          machine.succeed(
+              "curl -sSfL http://localhost:3000/ | grep '<title>Zammad Helpdesk</title>'"
+          )
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/zeronet-conservancy.nix b/nixpkgs/nixos/tests/zeronet-conservancy.nix
new file mode 100644
index 000000000000..8cb649cbdaab
--- /dev/null
+++ b/nixpkgs/nixos/tests/zeronet-conservancy.nix
@@ -0,0 +1,25 @@
+let
+  port = 43110;
+in
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "zeronet-conservancy";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ fgaz ];
+  };
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.zeronet = {
+      enable = true;
+      package = pkgs.zeronet-conservancy;
+      inherit port;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("zeronet.service")
+
+    machine.wait_for_open_port(${toString port})
+
+    machine.succeed("curl --fail -H 'Accept: text/html, application/xml, */*' localhost:${toString port}/Stats")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/zfs.nix b/nixpkgs/nixos/tests/zfs.nix
new file mode 100644
index 000000000000..0b411b0b9d8a
--- /dev/null
+++ b/nixpkgs/nixos/tests/zfs.nix
@@ -0,0 +1,268 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+
+  makeZfsTest = name:
+    { kernelPackages
+    , enableSystemdStage1 ? false
+    , zfsPackage
+    , extraTest ? ""
+    }:
+    makeTest {
+      name = "zfs-" + name;
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ elvishjerricco ];
+      };
+
+      nodes.machine = { config, pkgs, lib, ... }:
+        let
+          usersharePath = "/var/lib/samba/usershares";
+        in {
+        virtualisation = {
+          emptyDiskImages = [ 4096 4096 ];
+          useBootLoader = true;
+          useEFIBoot = true;
+        };
+        boot.loader.systemd-boot.enable = true;
+        boot.loader.timeout = 0;
+        boot.loader.efi.canTouchEfiVariables = true;
+        networking.hostId = "deadbeef";
+        boot.kernelPackages = kernelPackages;
+        boot.zfs.package = zfsPackage;
+        boot.supportedFilesystems = [ "zfs" ];
+        boot.initrd.systemd.enable = enableSystemdStage1;
+
+        environment.systemPackages = [ pkgs.parted ];
+
+        # /dev/disk/by-id doesn't get populated in the NixOS test framework
+        boot.zfs.devNodes = "/dev/disk/by-uuid";
+
+        specialisation.samba.configuration = {
+          services.samba = {
+            enable = true;
+            extraConfig = ''
+              registry shares = yes
+              usershare path = ${usersharePath}
+              usershare allow guests = yes
+              usershare max shares = 100
+              usershare owner only = no
+            '';
+          };
+          systemd.services.samba-smbd.serviceConfig.ExecStartPre =
+            "${pkgs.coreutils}/bin/mkdir -m +t -p ${usersharePath}";
+          virtualisation.fileSystems = {
+            "/tmp/mnt" = {
+              device = "rpool/root";
+              fsType = "zfs";
+            };
+          };
+        };
+
+        specialisation.encryption.configuration = {
+          boot.zfs.requestEncryptionCredentials = [ "automatic" ];
+          virtualisation.fileSystems."/automatic" = {
+            device = "automatic";
+            fsType = "zfs";
+          };
+          virtualisation.fileSystems."/manual" = {
+            device = "manual";
+            fsType = "zfs";
+          };
+          virtualisation.fileSystems."/manual/encrypted" = {
+            device = "manual/encrypted";
+            fsType = "zfs";
+            options = [ "noauto" ];
+          };
+          virtualisation.fileSystems."/manual/httpkey" = {
+            device = "manual/httpkey";
+            fsType = "zfs";
+            options = [ "noauto" ];
+          };
+        };
+
+        specialisation.forcepool.configuration = {
+          systemd.services.zfs-import-forcepool.wantedBy = lib.mkVMOverride [ "forcepool.mount" ];
+          systemd.targets.zfs.wantedBy = lib.mkVMOverride [];
+          boot.zfs.forceImportAll = true;
+          virtualisation.fileSystems."/forcepool" = {
+            device = "forcepool";
+            fsType = "zfs";
+            options = [ "noauto" ];
+          };
+        };
+
+        services.nginx = {
+          enable = true;
+          virtualHosts = {
+            localhost = {
+              locations = {
+                "/zfskey" = {
+                  return = ''200 "httpkeyabc"'';
+                };
+              };
+            };
+          };
+        };
+      };
+
+      testScript = ''
+        machine.wait_for_unit("multi-user.target")
+        machine.succeed(
+            "zpool status",
+            "parted --script /dev/vdb mklabel msdos",
+            "parted --script /dev/vdb -- mkpart primary 1024M -1s",
+            "parted --script /dev/vdc mklabel msdos",
+            "parted --script /dev/vdc -- mkpart primary 1024M -1s",
+        )
+
+        with subtest("sharesmb works"):
+            machine.succeed(
+                "zpool create rpool /dev/vdb1",
+                "zfs create -o mountpoint=legacy rpool/root",
+                # shared datasets cannot have legacy mountpoint
+                "zfs create rpool/shared_smb",
+                "bootctl set-default nixos-generation-1-specialisation-samba.conf",
+                "sync",
+            )
+            machine.crash()
+            machine.wait_for_unit("multi-user.target")
+            machine.succeed("zfs set sharesmb=on rpool/shared_smb")
+            machine.succeed(
+                "smbclient -gNL localhost | grep rpool_shared_smb",
+                "umount /tmp/mnt",
+                "zpool destroy rpool",
+            )
+
+        with subtest("encryption works"):
+            machine.succeed(
+                'echo password | zpool create -O mountpoint=legacy '
+                + "-O encryption=aes-256-gcm -O keyformat=passphrase automatic /dev/vdb1",
+                "zpool create -O mountpoint=legacy manual /dev/vdc1",
+                "echo otherpass | zfs create "
+                + "-o encryption=aes-256-gcm -o keyformat=passphrase manual/encrypted",
+                "zfs create -o encryption=aes-256-gcm -o keyformat=passphrase "
+                + "-o keylocation=http://localhost/zfskey manual/httpkey",
+                "bootctl set-default nixos-generation-1-specialisation-encryption.conf",
+                "sync",
+                "zpool export automatic",
+                "zpool export manual",
+            )
+            machine.crash()
+            machine.start()
+            machine.wait_for_console_text("Starting password query on")
+            machine.send_console("password\n")
+            machine.wait_for_unit("multi-user.target")
+            machine.succeed(
+                "zfs get -Ho value keystatus manual/encrypted | grep -Fx unavailable",
+                "echo otherpass | zfs load-key manual/encrypted",
+                "systemctl start manual-encrypted.mount",
+                "zfs load-key manual/httpkey",
+                "systemctl start manual-httpkey.mount",
+                "umount /automatic /manual/encrypted /manual/httpkey /manual",
+                "zpool destroy automatic",
+                "zpool destroy manual",
+            )
+
+        with subtest("boot.zfs.forceImportAll works"):
+            machine.succeed(
+                "rm /etc/hostid",
+                "zgenhostid deadcafe",
+                "zpool create forcepool /dev/vdb1 -O mountpoint=legacy",
+                "bootctl set-default nixos-generation-1-specialisation-forcepool.conf",
+                "rm /etc/hostid",
+                "sync",
+            )
+            machine.crash()
+            machine.wait_for_unit("multi-user.target")
+            machine.fail("zpool import forcepool")
+            machine.succeed(
+                "systemctl start forcepool.mount",
+                "mount | grep forcepool",
+            )
+      '' + extraTest;
+
+    };
+
+
+in {
+
+  # maintainer: @raitobezarius
+  series_2_1 = makeZfsTest "2.1-series" {
+    zfsPackage = pkgs.zfs_2_1;
+    kernelPackages = pkgs.linuxPackages;
+  };
+
+  stable = makeZfsTest "stable" {
+    zfsPackage = pkgs.zfsStable;
+    kernelPackages = pkgs.linuxPackages;
+  };
+
+  unstable = makeZfsTest "unstable" rec {
+    zfsPackage = pkgs.zfsUnstable;
+    kernelPackages = zfsPackage.latestCompatibleLinuxPackages;
+  };
+
+  unstableWithSystemdStage1 = makeZfsTest "unstable" rec {
+    zfsPackage = pkgs.zfsUnstable;
+    kernelPackages = zfsPackage.latestCompatibleLinuxPackages;
+    enableSystemdStage1 = true;
+  };
+
+  installerBoot = (import ./installer.nix { }).separateBootZfs;
+  installer = (import ./installer.nix { }).zfsroot;
+
+  expand-partitions = makeTest {
+    name = "multi-disk-zfs";
+    nodes = {
+      machine = { pkgs, ... }: {
+        environment.systemPackages = [ pkgs.parted ];
+        boot.supportedFilesystems = [ "zfs" ];
+        networking.hostId = "00000000";
+
+        virtualisation = {
+          emptyDiskImages = [ 20480 20480 20480 20480 20480 20480 ];
+        };
+
+        specialisation.resize.configuration = {
+          services.zfs.expandOnBoot = [ "tank" ];
+        };
+      };
+    };
+
+    testScript = { nodes, ... }:
+      ''
+        start_all()
+        machine.wait_for_unit("default.target")
+        print(machine.succeed('mount'))
+
+        print(machine.succeed('parted --script /dev/vdb -- mklabel gpt'))
+        print(machine.succeed('parted --script /dev/vdb -- mkpart primary 1M 70M'))
+
+        print(machine.succeed('parted --script /dev/vdc -- mklabel gpt'))
+        print(machine.succeed('parted --script /dev/vdc -- mkpart primary 1M 70M'))
+
+        print(machine.succeed('zpool create tank mirror /dev/vdb1 /dev/vdc1 mirror /dev/vdd /dev/vde mirror /dev/vdf /dev/vdg'))
+        print(machine.succeed('zpool list -v'))
+        print(machine.succeed('mount'))
+        start_size = int(machine.succeed('df -k --output=size /tank | tail -n1').strip())
+
+        print(machine.succeed("/run/current-system/specialisation/resize/bin/switch-to-configuration test >&2"))
+        machine.wait_for_unit("zpool-expand-pools.service")
+        machine.wait_for_unit("zpool-expand@tank.service")
+
+        print(machine.succeed('zpool list -v'))
+        new_size = int(machine.succeed('df -k --output=size /tank | tail -n1').strip())
+
+        if (new_size - start_size) > 20000000:
+          print("Disk grew appropriately.")
+        else:
+          print(f"Disk went from {start_size} to {new_size}, which doesn't seem right.")
+          exit(1)
+      '';
+  };
+}
diff --git a/nixpkgs/nixos/tests/zigbee2mqtt.nix b/nixpkgs/nixos/tests/zigbee2mqtt.nix
new file mode 100644
index 000000000000..9d6d03a4b9bb
--- /dev/null
+++ b/nixpkgs/nixos/tests/zigbee2mqtt.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  {
+    name = "zigbee2mqtt";
+    nodes.machine = { pkgs, ... }:
+      {
+        systemd.services.dummy-serial = {
+          wantedBy = [
+            "multi-user.target"
+          ];
+          serviceConfig = {
+            ExecStart = "${pkgs.socat}/bin/socat pty,link=/dev/ttyACM0,mode=666 pty,link=/dev/ttyACM1";
+          };
+        };
+
+        services.zigbee2mqtt = {
+          enable = true;
+        };
+
+        systemd.services.zigbee2mqtt.serviceConfig.DevicePolicy = lib.mkForce "auto";
+      };
+
+    testScript = ''
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_until_fails("systemctl status zigbee2mqtt.service")
+      machine.succeed(
+          "journalctl -eu zigbee2mqtt | grep 'Failed to connect to the adapter'"
+      )
+
+      machine.log(machine.succeed("systemd-analyze security zigbee2mqtt.service"))
+    '';
+  }
+)
diff --git a/nixpkgs/nixos/tests/zoneminder.nix b/nixpkgs/nixos/tests/zoneminder.nix
new file mode 100644
index 000000000000..3c97bc8282d2
--- /dev/null
+++ b/nixpkgs/nixos/tests/zoneminder.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ lib, ...}:
+
+{
+  name = "zoneminder";
+  meta.maintainers = with lib.maintainers; [ danielfullmer ];
+
+  nodes.machine = { ... }:
+  {
+    services.zoneminder = {
+      enable = true;
+      database.createLocally = true;
+      database.username = "zoneminder";
+    };
+    time.timeZone = "America/New_York";
+  };
+
+  testScript = ''
+    machine.wait_for_unit("zoneminder.service")
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_open_port(8095)
+    machine.succeed("curl --fail http://localhost:8095/")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/zookeeper.nix b/nixpkgs/nixos/tests/zookeeper.nix
new file mode 100644
index 000000000000..0ee2673886a7
--- /dev/null
+++ b/nixpkgs/nixos/tests/zookeeper.nix
@@ -0,0 +1,46 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+let
+
+  perlEnv = pkgs.perl.withPackages (p: [p.NetZooKeeper]);
+
+in {
+  name = "zookeeper";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ nequissimus ztzg ];
+  };
+
+  nodes = {
+    server = { ... }: {
+      services.zookeeper = {
+        enable = true;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 2181 ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("zookeeper")
+    server.wait_for_unit("network.target")
+    server.wait_for_open_port(2181)
+
+    server.wait_until_succeeds(
+        "${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 create /foo bar"
+    )
+    server.wait_until_succeeds(
+        "${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 set /foo hello"
+    )
+    server.wait_until_succeeds(
+        "${pkgs.zookeeper}/bin/zkCli.sh -server localhost:2181 get /foo | grep hello"
+    )
+
+    server.wait_until_succeeds(
+        "${perlEnv}/bin/perl -E 'use Net::ZooKeeper qw(:acls); $z=Net::ZooKeeper->new(q(localhost:2181)); $z->create(qw(/perl foo acl), ZOO_OPEN_ACL_UNSAFE) || die $z->get_error()'"
+    )
+    server.wait_until_succeeds(
+        "${perlEnv}/bin/perl -E 'use Net::ZooKeeper qw(:acls); $z=Net::ZooKeeper->new(q(localhost:2181)); $z->get(qw(/perl)) eq qw(foo) || die $z->get_error()'"
+    )
+  '';
+})
diff --git a/nixpkgs/nixos/tests/zram-generator.nix b/nixpkgs/nixos/tests/zram-generator.nix
new file mode 100644
index 000000000000..2be7bd2e05b1
--- /dev/null
+++ b/nixpkgs/nixos/tests/zram-generator.nix
@@ -0,0 +1,42 @@
+import ./make-test-python.nix {
+  name = "zram-generator";
+
+  nodes = {
+    single = { ... }: {
+      virtualisation = {
+        emptyDiskImages = [ 512 ];
+      };
+      zramSwap = {
+        enable = true;
+        priority = 10;
+        algorithm = "lz4";
+        swapDevices = 1;
+        memoryPercent = 30;
+        memoryMax = 10 * 1024 * 1024;
+        writebackDevice = "/dev/vdb";
+      };
+    };
+    machine = { ... }: {
+      zramSwap = {
+        enable = true;
+        priority = 10;
+        algorithm = "lz4";
+        swapDevices = 2;
+        memoryPercent = 30;
+        memoryMax = 10 * 1024 * 1024;
+      };
+    };
+  };
+
+  testScript = ''
+    single.wait_for_unit("systemd-zram-setup@zram0.service")
+
+    machine.wait_for_unit("systemd-zram-setup@zram0.service")
+    machine.wait_for_unit("systemd-zram-setup@zram1.service")
+    zram = machine.succeed("zramctl --noheadings --raw")
+    swap = machine.succeed("swapon --show --noheadings")
+    for i in range(2):
+        assert f"/dev/zram{i} lz4 10M" in zram
+        assert f"/dev/zram{i} partition  10M" in swap
+  '';
+}
diff --git a/nixpkgs/nixos/tests/zrepl.nix b/nixpkgs/nixos/tests/zrepl.nix
new file mode 100644
index 000000000000..bdf11122c73f
--- /dev/null
+++ b/nixpkgs/nixos/tests/zrepl.nix
@@ -0,0 +1,69 @@
+import ./make-test-python.nix (
+  {
+    name = "zrepl";
+
+    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.systemctl("start network-online.target")
+          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_start_time" in out
+          ), "zrepl start time 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"
+    '';
+  })
diff --git a/nixpkgs/nixos/tests/zsh-history.nix b/nixpkgs/nixos/tests/zsh-history.nix
new file mode 100644
index 000000000000..64f32a07e215
--- /dev/null
+++ b/nixpkgs/nixos/tests/zsh-history.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "zsh-history";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ ];
+  };
+
+  nodes.default = { ... }: {
+    programs = {
+      zsh.enable = true;
+    };
+    environment.systemPackages = [ pkgs.zsh-history ];
+    programs.zsh.interactiveShellInit = ''
+      source ${pkgs.zsh-history.out}/share/zsh/init.zsh
+    '';
+    users.users.root.shell = "${pkgs.zsh}/bin/zsh";
+  };
+
+  testScript = ''
+    start_all()
+    default.wait_for_unit("multi-user.target")
+    default.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+    # Login
+    default.wait_until_tty_matches("1", "login: ")
+    default.send_chars("root\n")
+    default.wait_until_tty_matches("1", r"\nroot@default\b")
+
+    # Generate some history
+    default.send_chars("echo foobar\n")
+    default.wait_until_tty_matches("1", "foobar")
+
+    # Ensure that command was recorded in history
+    default.succeed("/run/current-system/sw/bin/history list | grep -q foobar")
+  '';
+})
diff --git a/nixpkgs/nixos/tests/zwave-js.nix b/nixpkgs/nixos/tests/zwave-js.nix
new file mode 100644
index 000000000000..9239e6964fd7
--- /dev/null
+++ b/nixpkgs/nixos/tests/zwave-js.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+let
+  secretsConfigFile = pkgs.writeText "secrets.json" (builtins.toJSON {
+    securityKeys = {
+      "S0_Legacy" = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+    };
+  });
+in {
+  name = "zwave-js";
+  meta.maintainers = with lib.maintainers; [ graham33 ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.zwave-js = {
+        enable = true;
+        serialPort = "/dev/null";
+        extraFlags = ["--mock-driver"];
+        inherit secretsConfigFile;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("zwave-js.service")
+    machine.wait_for_open_port(3000)
+    machine.wait_until_succeeds("journalctl --since -1m --unit zwave-js --grep 'ZwaveJS server listening'")
+  '';
+})