about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md4
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.section.md35
-rw-r--r--nixos/doc/manual/from_md/development/option-declarations.section.xml4
-rw-r--r--nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml39
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2205.section.xml22
-rw-r--r--nixos/doc/manual/from_md/release-notes/rl-2211.section.xml29
-rw-r--r--nixos/doc/manual/release-notes/rl-2205.section.md11
-rw-r--r--nixos/doc/manual/release-notes/rl-2211.section.md9
-rw-r--r--nixos/lib/test-driver/default.nix16
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py2
-rw-r--r--nixos/lib/test-driver/test_driver/py.typed0
-rw-r--r--nixos/lib/test-script-prepend.py42
-rw-r--r--nixos/lib/testing-python.nix37
-rw-r--r--nixos/modules/hardware/video/nvidia.nix31
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/backup/restic.nix174
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix6
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix2
-rw-r--r--nixos/modules/services/games/asf.nix89
-rw-r--r--nixos/modules/services/misc/uhub.nix14
-rw-r--r--nixos/modules/services/monitoring/netdata.nix2
-rw-r--r--nixos/modules/services/networking/unifi.nix6
-rw-r--r--nixos/modules/services/security/infnoise.nix60
-rw-r--r--nixos/modules/services/web-apps/peertube.nix16
-rw-r--r--nixos/modules/services/x11/desktop-managers/phosh.nix8
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix5
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix3
-rw-r--r--nixos/modules/tasks/network-interfaces.nix16
-rw-r--r--nixos/modules/virtualisation/docker-rootless.nix1
-rw-r--r--nixos/modules/virtualisation/docker.nix1
-rw-r--r--nixos/release.nix13
-rw-r--r--nixos/tests/all-tests.nix6
-rw-r--r--nixos/tests/docker-edge.nix49
-rw-r--r--nixos/tests/ecryptfs.nix10
-rw-r--r--nixos/tests/extra-python-packages.nix13
-rw-r--r--nixos/tests/firefox.nix6
-rw-r--r--nixos/tests/home-assistant.nix1
-rw-r--r--nixos/tests/ihatemoney/default.nix9
-rw-r--r--nixos/tests/login.nix6
-rw-r--r--nixos/tests/matrix/mjolnir.nix1
-rw-r--r--nixos/tests/networking.nix29
-rw-r--r--nixos/tests/nitter.nix2
-rw-r--r--nixos/tests/pam/pam-oath-login.nix22
-rw-r--r--nixos/tests/restic.nix191
-rw-r--r--nixos/tests/shadow.nix24
-rw-r--r--nixos/tests/sway.nix6
-rw-r--r--nixos/tests/uptermd.nix2
-rw-r--r--nixos/tests/user-activation-scripts.nix4
-rw-r--r--nixos/tests/user-home-mode.nix4
-rw-r--r--nixos/tests/web-apps/peertube.nix7
-rw-r--r--nixos/tests/zsh-history.nix6
51 files changed, 756 insertions, 340 deletions
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index 2e11218e82389..ef7255557a109 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixos/doc/manual/development/option-declarations.section.md
@@ -120,14 +120,14 @@ lib.mkOption {
 ```nix
 lib.mkPackageOption pkgs "GHC" {
   default = [ "ghc" ];
-  example = "pkgs.haskell.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])";
+  example = "pkgs.haskell.package.ghc923.ghc.withPackages (hkgs: [ hkgs.primes ])";
 }
 # is like
 lib.mkOption {
   type = lib.types.package;
   default = pkgs.ghc;
   defaultText = lib.literalExpression "pkgs.ghc";
-  example = lib.literalExpression "pkgs.haskell.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])";
+  example = lib.literalExpression "pkgs.haskell.package.ghc923.ghc.withPackages (hkgs: [ hkgs.primes ])";
   description = "The GHC package to use.";
 }
 ```
diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md
index e5ee1cb01ff16..f4f4056ad9889 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/nixos/doc/manual/development/writing-nixos-tests.section.md
@@ -332,6 +332,19 @@ repository):
     '';
 ```
 
+Similarly, the type checking of test scripts can be disabled in the following
+way:
+
+```nix
+import ./make-test-python.nix {
+  skipTypeCheck = true;
+  nodes.machine =
+    { config, pkgs, ... }:
+    { configuration…
+    };
+}
+```
+
 ## Failing tests early {#ssec-failing-tests-early}
 
 To fail tests early when certain invariables are no longer met (instead of waiting for the build to time out), the decorator `polling_condition` is provided. For example, if we are testing a program `foo` that should not quit after being started, we might write the following:
@@ -380,3 +393,25 @@ with foo_running:
     def foo_running():
         machine.succeed("pgrep -x foo")
     ```
+
+## Adding Python packages to the test script {#ssec-python-packages-in-test-script}
+
+When additional Python libraries are required in the test script, they can be
+added using the parameter `extraPythonPackages`. For example, you could add
+`numpy` like this:
+
+```nix
+import ./make-test-python.nix
+{
+  extraPythonPackages = p: [ p.numpy ];
+
+  nodes = { };
+
+  testScript = ''
+    import numpy as np
+    assert str(np.zeros(4) == "array([0., 0., 0., 0.])")
+  '';
+}
+```
+
+In that case, `numpy` is chosen from the generic `python3Packages`.
diff --git a/nixos/doc/manual/from_md/development/option-declarations.section.xml b/nixos/doc/manual/from_md/development/option-declarations.section.xml
index 91867c224107a..381163dd7c74d 100644
--- a/nixos/doc/manual/from_md/development/option-declarations.section.xml
+++ b/nixos/doc/manual/from_md/development/option-declarations.section.xml
@@ -183,14 +183,14 @@ lib.mkOption {
         <programlisting language="bash">
 lib.mkPackageOption pkgs &quot;GHC&quot; {
   default = [ &quot;ghc&quot; ];
-  example = &quot;pkgs.haskell.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
+  example = &quot;pkgs.haskell.package.ghc923.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
 }
 # is like
 lib.mkOption {
   type = lib.types.package;
   default = pkgs.ghc;
   defaultText = lib.literalExpression &quot;pkgs.ghc&quot;;
-  example = lib.literalExpression &quot;pkgs.haskell.package.ghc922.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
+  example = lib.literalExpression &quot;pkgs.haskell.package.ghc923.ghc.withPackages (hkgs: [ hkgs.primes ])&quot;;
   description = &quot;The GHC package to use.&quot;;
 }
 </programlisting>
diff --git a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
index 7ce3e4cb29065..46367bdd345d1 100644
--- a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
+++ b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
@@ -590,6 +590,19 @@ import ./make-test-python.nix {
       # fmt: on
     '';
 </programlisting>
+    <para>
+      Similarly, the type checking of test scripts can be disabled in
+      the following way:
+    </para>
+    <programlisting language="bash">
+import ./make-test-python.nix {
+  skipTypeCheck = true;
+  nodes.machine =
+    { config, pkgs, ... }:
+    { configuration…
+    };
+}
+</programlisting>
   </section>
   <section xml:id="ssec-failing-tests-early">
     <title>Failing tests early</title>
@@ -652,4 +665,30 @@ def foo_running():
 ```
 </programlisting>
   </section>
+  <section xml:id="ssec-python-packages-in-test-script">
+    <title>Adding Python packages to the test script</title>
+    <para>
+      When additional Python libraries are required in the test script,
+      they can be added using the parameter
+      <literal>extraPythonPackages</literal>. For example, you could add
+      <literal>numpy</literal> like this:
+    </para>
+    <programlisting language="bash">
+import ./make-test-python.nix
+{
+  extraPythonPackages = p: [ p.numpy ];
+
+  nodes = { };
+
+  testScript = ''
+    import numpy as np
+    assert str(np.zeros(4) == &quot;array([0., 0., 0., 0.])&quot;)
+  '';
+}
+</programlisting>
+    <para>
+      In that case, <literal>numpy</literal> is chosen from the generic
+      <literal>python3Packages</literal>.
+    </para>
+  </section>
 </section>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 8c090e6cc1a09..5208671e4dab0 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -2102,6 +2102,28 @@ cp /var/lib/redis/dump.rdb &quot;/var/lib/redis-mastodon/dump.rdb&quot;
       </listitem>
       <listitem>
         <para>
+          Peertube now uses services.redis.servers to start a new redis
+          server, instead of using a global redis server. This improves
+          compatibility with other services that use redis.
+        </para>
+        <para>
+          Redis database is used for storage only cache and job queue.
+          More information can be found here -
+          <link xlink:href="https://docs.joinpeertube.org/contribute-architecture">Peertube
+          architecture</link>.
+        </para>
+        <para>
+          If you do want to save the redis database, you can use the
+          following commands before upgrade OS:
+        </para>
+        <programlisting language="bash">
+redis-cli save
+sudo mkdir /var/lib/redis-peertube
+sudo cp /var/lib/redis/dump.rdb /var/lib/redis-peertube/dump.rdb
+</programlisting>
+      </listitem>
+      <listitem>
+        <para>
           If you are using Wayland you can choose to use the Ozone
           Wayland support in Chrome and several Electron apps by setting
           the environment variable <literal>NIXOS_OZONE_WL=1</literal>
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index 55c20427cfdef..65ba6033c8341 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -40,7 +40,7 @@
   </section>
   <section xml:id="sec-release-22.11-new-services">
     <title>New Services</title>
-    <itemizedlist spacing="compact">
+    <itemizedlist>
       <listitem>
         <para>
           <link xlink:href="https://github.com/jollheef/appvm">appvm</link>,
@@ -48,6 +48,13 @@
           <link xlink:href="options.html#opt-virtualisation.appvm.enable">virtualisation.appvm</link>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          <link xlink:href="https://github.com/leetronics/infnoise">infnoise</link>,
+          a hardware True Random Number Generator dongle. Available as
+          <link xlink:href="options.html#opt-services.infnoise.enable">services.infnoise</link>.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
   <section xml:id="sec-release-22.11-incompatibilities">
@@ -89,7 +96,7 @@
   </section>
   <section xml:id="sec-release-22.11-notable-changes">
     <title>Other Notable Changes</title>
-    <itemizedlist spacing="compact">
+    <itemizedlist>
       <listitem>
         <para>
           A new module was added for the Saleae Logic device family,
@@ -98,6 +105,24 @@
           <literal>hardware.saleae-logic.package</literal>.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          Matrix Synapse now requires entries in the
+          <literal>state_group_edges</literal> table to be unique, in
+          order to prevent accidentally introducing duplicate
+          information (for example, because a database backup was
+          restored multiple times). If your Synapse database already has
+          duplicate rows in this table, this could fail with an error
+          and require manual remediation.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
+          memtest86+ was updated from 5.00-coreboot-002 to 6.00-beta2.
+          It is now the upstream version from https://www.memtest.org/,
+          as coreboot’s fork is no longer available.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index e4dc540eaafe1..faf941f569966 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -765,6 +765,17 @@ In addition to numerous new and upgraded packages, this release has the followin
   redis-cli save
   cp /var/lib/redis/dump.rdb "/var/lib/redis-mastodon/dump.rdb"
   ```
+- Peertube now uses services.redis.servers to start a new redis server, instead of using a global redis server.
+  This improves compatibility with other services that use redis.
+
+  Redis database is used for storage only cache and job queue. More information can be found here - [Peertube architecture](https://docs.joinpeertube.org/contribute-architecture).
+
+  If you do want to save the redis database, you can use the following commands before upgrade OS:
+  ```bash
+  redis-cli save
+  sudo mkdir /var/lib/redis-peertube
+  sudo cp /var/lib/redis/dump.rdb /var/lib/redis-peertube/dump.rdb
+  ```
 
 - If you are using Wayland you can choose to use the Ozone Wayland support
   in Chrome and several Electron apps by setting the environment variable
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 97ecb725dfb5c..864fe5c693460 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -25,6 +25,9 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [appvm](https://github.com/jollheef/appvm), Nix based app VMs. Available as [virtualisation.appvm](options.html#opt-virtualisation.appvm.enable).
 
+- [infnoise](https://github.com/leetronics/infnoise), a hardware True Random Number Generator dongle.
+  Available as [services.infnoise](options.html#opt-services.infnoise.enable).
+
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
 ## Backward Incompatibilities {#sec-release-22.11-incompatibilities}
@@ -44,6 +47,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 ## Other Notable Changes {#sec-release-22.11-notable-changes}
 
-* A new module was added for the Saleae Logic device family, providing the options `hardware.saleae-logic.enable` and `hardware.saleae-logic.package`.
+- A new module was added for the Saleae Logic device family, providing the options `hardware.saleae-logic.enable` and `hardware.saleae-logic.package`.
+
+- Matrix Synapse now requires entries in the `state_group_edges` table to be unique, in order to prevent accidentally introducing duplicate information (for example, because a database backup was restored multiple times). If your Synapse database already has duplicate rows in this table, this could fail with an error and require manual remediation.
+
+- memtest86+ was updated from 5.00-coreboot-002 to 6.00-beta2. It is now the upstream version from https://www.memtest.org/, as coreboot's fork is no longer available.
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
diff --git a/nixos/lib/test-driver/default.nix b/nixos/lib/test-driver/default.nix
index 3aee913431890..e3786622c3c58 100644
--- a/nixos/lib/test-driver/default.nix
+++ b/nixos/lib/test-driver/default.nix
@@ -10,6 +10,7 @@
 , socat
 , tesseract4
 , vde2
+, extraPythonPackages ? (_ : [])
 }:
 
 python3Packages.buildPythonApplication rec {
@@ -17,14 +18,25 @@ python3Packages.buildPythonApplication rec {
   version = "1.1";
   src = ./.;
 
-  propagatedBuildInputs = [ coreutils netpbm python3Packages.colorama python3Packages.ptpython qemu_pkg socat vde2 ]
-    ++ (lib.optionals enableOCR [ imagemagick_light tesseract4 ]);
+  propagatedBuildInputs = [
+    coreutils
+    netpbm
+    python3Packages.colorama
+    python3Packages.ptpython
+    qemu_pkg
+    socat
+    vde2
+  ]
+    ++ (lib.optionals enableOCR [ imagemagick_light tesseract4 ])
+    ++ extraPythonPackages python3Packages;
 
   doCheck = true;
   checkInputs = with python3Packages; [ mypy pylint black ];
   checkPhase = ''
     mypy --disallow-untyped-defs \
           --no-implicit-optional \
+          --pretty \
+          --no-color-output \
           --ignore-missing-imports ${src}/test_driver
     pylint --errors-only --enable=unused-import ${src}/test_driver
     black --check --diff ${src}/test_driver
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index 035e3ffe89731..3ff3cf5645f82 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -682,7 +682,7 @@ class Machine:
         with self.nested("waiting for {} to appear on tty {}".format(regexp, tty)):
             retry(tty_matches)
 
-    def send_chars(self, chars: List[str]) -> None:
+    def send_chars(self, chars: str) -> None:
         with self.nested("sending keys ‘{}‘".format(chars)):
             for char in chars:
                 self.send_key(char)
diff --git a/nixos/lib/test-driver/test_driver/py.typed b/nixos/lib/test-driver/test_driver/py.typed
new file mode 100644
index 0000000000000..e69de29bb2d1d
--- /dev/null
+++ b/nixos/lib/test-driver/test_driver/py.typed
diff --git a/nixos/lib/test-script-prepend.py b/nixos/lib/test-script-prepend.py
new file mode 100644
index 0000000000000..15e59ce01047d
--- /dev/null
+++ b/nixos/lib/test-script-prepend.py
@@ -0,0 +1,42 @@
+# This file contains type hints that can be prepended to Nix test scripts so they can be type
+# checked.
+
+from test_driver.driver import Driver
+from test_driver.vlan import VLan
+from test_driver.machine import Machine
+from test_driver.logger import Logger
+from typing import Callable, Iterator, ContextManager, Optional, List, Dict, Any, Union
+from typing_extensions import Protocol
+from pathlib import Path
+
+
+class RetryProtocol(Protocol):
+    def __call__(self, fn: Callable, timeout: int = 900) -> None:
+        raise Exception("This is just type information for the Nix test driver")
+
+
+class PollingConditionProtocol(Protocol):
+    def __call__(
+        self,
+        fun_: Optional[Callable] = None,
+        *,
+        seconds_interval: float = 2.0,
+        description: Optional[str] = None,
+    ) -> Union[Callable[[Callable], ContextManager], ContextManager]:
+        raise Exception("This is just type information for the Nix test driver")
+
+
+start_all: Callable[[], None]
+subtest: Callable[[str], ContextManager[None]]
+retry: RetryProtocol
+test_script: Callable[[], None]
+machines: List[Machine]
+vlans: List[VLan]
+driver: Driver
+log: Logger
+create_machine: Callable[[Dict[str, Any]], Machine]
+run_tests: Callable[[], None]
+join_all: Callable[[], None]
+serial_stdout_off: Callable[[], None]
+serial_stdout_on: Callable[[], None]
+polling_condition: PollingConditionProtocol
diff --git a/nixos/lib/testing-python.nix b/nixos/lib/testing-python.nix
index c1015ec0aca0c..a6868a708aaf3 100644
--- a/nixos/lib/testing-python.nix
+++ b/nixos/lib/testing-python.nix
@@ -50,14 +50,16 @@ rec {
     , qemu_pkg ? pkgs.qemu_test
     , enableOCR ? false
     , skipLint ? false
+    , skipTypeCheck ? false
     , passthru ? {}
     , interactive ? false
+    , extraPythonPackages ? (_ :[])
   }:
     let
       # Reifies and correctly wraps the python test driver for
       # the respective qemu version and with or without ocr support
       testDriver = pkgs.callPackage ./test-driver {
-        inherit enableOCR;
+        inherit enableOCR extraPythonPackages;
         qemu_pkg = qemu_test;
         imagemagick_light = imagemagick_light.override { inherit libtiff; };
         tesseract4 = tesseract4.override { enableLanguages = [ "eng" ]; };
@@ -85,7 +87,7 @@ rec {
 
       nodeHostNames = let
         nodesList = map (c: c.config.system.name) (lib.attrValues nodes);
-      in nodesList ++ lib.optional (lib.length nodesList == 1) "machine";
+      in nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
 
       # TODO: This is an implementation error and needs fixing
       # the testing famework cannot legitimately restrict hostnames further
@@ -100,6 +102,9 @@ rec {
         then testScript { inherit nodes; }
         else testScript;
 
+      uniqueVlans = lib.unique (builtins.concatLists vlans);
+      vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
+      machineNames = map (name: "${name}: Machine;") nodeHostNames;
     in
     if lib.length invalidNodeNames > 0 then
       throw ''
@@ -113,7 +118,7 @@ rec {
     else lib.warnIf skipLint "Linting is disabled" (runCommand testDriverName
       {
         inherit testName;
-        nativeBuildInputs = [ makeWrapper ];
+        nativeBuildInputs = [ makeWrapper mypy ];
         testScript = testScript';
         preferLocalBuild = true;
         passthru = passthru // {
@@ -125,7 +130,25 @@ rec {
         mkdir -p $out/bin
 
         vmStartScripts=($(for i in ${toString vms}; do echo $i/bin/run-*-vm; done))
-        echo -n "$testScript" > $out/test-script
+
+        ${lib.optionalString (!skipTypeCheck) ''
+          # prepend type hints so the test script can be type checked with mypy
+          cat "${./test-script-prepend.py}" >> testScriptWithTypes
+          echo "${builtins.toString machineNames}" >> testScriptWithTypes
+          echo "${builtins.toString vlanNames}" >> testScriptWithTypes
+          echo -n "$testScript" >> testScriptWithTypes
+
+          # set pythonpath so mypy knows where to find the imports. this requires the py.typed file.
+          export PYTHONPATH='${./test-driver}'
+          mypy  --no-implicit-optional \
+                --pretty \
+                --no-color-output \
+                testScriptWithTypes
+          unset PYTHONPATH
+        ''}
+
+        echo -n "$testScript" >> $out/test-script
+
         ln -s ${testDriver}/bin/nixos-test-driver $out/bin/nixos-test-driver
 
         ${testDriver}/bin/generate-driver-symbols
@@ -152,6 +175,7 @@ rec {
     , testScript
     , enableOCR ? false
     , name ? "unnamed"
+    , skipTypeCheck ? false
       # Skip linting (mainly intended for faster dev cycles)
     , skipLint ? false
     , passthru ? {}
@@ -161,6 +185,7 @@ rec {
         (if meta.description or null != null
           then builtins.unsafeGetAttrPos "description" meta
           else builtins.unsafeGetAttrPos "testScript" t)
+    , extraPythonPackages ? (_ : [])
     } @ t:
     let
       mkNodes = qemu_pkg:
@@ -213,13 +238,13 @@ rec {
           );
 
       driver = setupDriverForTest {
-        inherit testScript enableOCR skipLint passthru;
+        inherit testScript enableOCR skipTypeCheck skipLint passthru extraPythonPackages;
         testName = name;
         qemu_pkg = pkgs.qemu_test;
         nodes = mkNodes pkgs.qemu_test;
       };
       driverInteractive = setupDriverForTest {
-        inherit testScript enableOCR skipLint passthru;
+        inherit testScript enableOCR skipTypeCheck skipLint passthru extraPythonPackages;
         testName = name;
         qemu_pkg = pkgs.qemu;
         nodes = mkNodes pkgs.qemu;
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 210d45ac84153..a9b04bcc85959 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -163,8 +163,19 @@ in
       '';
     };
 
+    hardware.nvidia.forceFullCompositionPipeline = lib.mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to force-enable the full composition pipeline.
+        This sometimes fixes screen tearing issues.
+        This has been reported to reduce the performance of some OpenGL applications and may produce issues in WebGL.
+        It also drastically increases the time the driver needs to clock down after load.
+      '';
+    };
+
     hardware.nvidia.package = lib.mkOption {
-      type = lib.types.package;
+      type = types.package;
       default = config.boot.kernelPackages.nvidiaPackages.stable;
       defaultText = literalExpression "config.boot.kernelPackages.nvidiaPackages.stable";
       description = ''
@@ -255,13 +266,18 @@ in
         ''
           BusID "${pCfg.nvidiaBusId}"
           ${optionalString syncCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
-          ${optionalString cfg.powerManagement.finegrained "Option \"NVreg_DynamicPowerManagement=0x02\""}
         '';
       screenSection =
         ''
           Option "RandRRotation" "on"
-          ${optionalString syncCfg.enable "Option \"AllowEmptyInitialConfiguration\""}
-        '';
+        '' + optionalString syncCfg.enable ''
+          Option "AllowEmptyInitialConfiguration"
+        '' + optionalString cfg.forceFullCompositionPipeline ''
+          Option         "metamodes" "nvidia-auto-select +0+0 {ForceFullCompositionPipeline=On}"
+          Option         "AllowIndirectGLXProtocol" "off"
+          Option         "TripleBuffer" "on"
+        ''
+        ;
     };
 
     services.xserver.serverLayoutSection = optionalString syncCfg.enable ''
@@ -367,7 +383,8 @@ in
           RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia%c{3} c 195 %c{3}"
         KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
         KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm-tools c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 1'"
-      '' + optionalString cfg.powerManagement.finegrained ''
+      '' + optionalString cfg.powerManagement.finegrained (
+      optionalString (versionOlder config.boot.kernelPackages.kernel.version "5.5") ''
         # Remove NVIDIA USB xHCI Host Controller devices, if present
         ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c0330", ATTR{remove}="1"
 
@@ -376,7 +393,7 @@ in
 
         # Remove NVIDIA Audio devices, if present
         ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{remove}="1"
-
+      '' + ''
         # Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
         ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
         ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
@@ -384,7 +401,7 @@ in
         # Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
         ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
         ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
-      '';
+      '');
 
     boot.extraModprobeConfig = mkIf cfg.powerManagement.finegrained ''
       options nvidia "NVreg_DynamicPowerManagement=0x02"
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 902fffd60f9b9..d59d7bfe40d9f 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -983,6 +983,7 @@
   ./services/security/hologram-server.nix
   ./services/security/hologram-agent.nix
   ./services/security/kanidm.nix
+  ./services/security/infnoise.nix
   ./services/security/munge.nix
   ./services/security/nginx-sso.nix
   ./services/security/oauth2_proxy.nix
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 8ff8e31864be2..333fdd494e3b9 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -96,13 +96,22 @@ in
         };
 
         repository = mkOption {
-          type = types.str;
+          type = with types; nullOr str;
+          default = null;
           description = ''
             repository to backup to.
           '';
           example = "sftp:backup@192.168.1.100:/backups/${name}";
         };
 
+        repositoryFile = mkOption {
+          type = with types; nullOr path;
+          default = null;
+          description = ''
+            Path to the file containing the repository location to backup to.
+          '';
+        };
+
         paths = mkOption {
           type = types.nullOr (types.listOf types.str);
           default = null;
@@ -142,7 +151,7 @@ in
 
         extraBackupArgs = mkOption {
           type = types.listOf types.str;
-          default = [];
+          default = [ ];
           description = ''
             Extra arguments passed to restic backup.
           '';
@@ -153,7 +162,7 @@ in
 
         extraOptions = mkOption {
           type = types.listOf types.str;
-          default = [];
+          default = [ ];
           description = ''
             Extra extended options to be passed to the restic --option flag.
           '';
@@ -172,7 +181,7 @@ in
 
         pruneOpts = mkOption {
           type = types.listOf types.str;
-          default = [];
+          default = [ ];
           description = ''
             A list of options (--keep-* et al.) for 'restic forget
             --prune', to automatically prune old snapshots.  The
@@ -197,9 +206,25 @@ in
           '';
           example = "find /home/matt/git -type d -name .git";
         };
+
+        backupPrepareCommand = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = ''
+            A script that must run before starting the backup process.
+          '';
+        };
+
+        backupCleanupCommand = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = ''
+            A script that must run after finishing the backup process.
+          '';
+        };
       };
     }));
-    default = {};
+    default = { };
     example = {
       localbackup = {
         paths = [ "/home" ];
@@ -225,66 +250,85 @@ in
   config = {
     warnings = mapAttrsToList (n: v: "services.restic.backups.${n}.s3CredentialsFile is deprecated, please use services.restic.backups.${n}.environmentFile instead.") (filterAttrs (n: v: v.s3CredentialsFile != null) config.services.restic.backups);
     systemd.services =
-      mapAttrs' (name: backup:
-        let
-          extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
-          resticCmd = "${pkgs.restic}/bin/restic${extraOptions}";
-          filesFromTmpFile = "/run/restic-backups-${name}/includes";
-          backupPaths = if (backup.dynamicFilesFrom == null)
-                        then if (backup.paths != null) then concatStringsSep " " backup.paths else ""
-                        else "--files-from ${filesFromTmpFile}";
-          pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
-            ( resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts) )
-            ( resticCmd + " check" )
-          ];
-          # Helper functions for rclone remotes
-          rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1;
-          rcloneAttrToOpt = v: "RCLONE_" + toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
-          rcloneAttrToConf = v: "RCLONE_CONFIG_" + toUpper (rcloneRemoteName + "_" + v);
-          toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
-        in nameValuePair "restic-backups-${name}" ({
-          environment = {
-            RESTIC_PASSWORD_FILE = backup.passwordFile;
-            RESTIC_REPOSITORY = backup.repository;
-          } // optionalAttrs (backup.rcloneOptions != null) (mapAttrs' (name: value:
-            nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
-          ) backup.rcloneOptions) // optionalAttrs (backup.rcloneConfigFile != null) {
-            RCLONE_CONFIG = backup.rcloneConfigFile;
-          } // optionalAttrs (backup.rcloneConfig != null) (mapAttrs' (name: value:
-            nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
-          ) backup.rcloneConfig);
-          path = [ pkgs.openssh ];
-          restartIfChanged = false;
-          serviceConfig = {
-            Type = "oneshot";
-            ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ])
-                        ++ pruneCmd;
-            User = backup.user;
-            RuntimeDirectory = "restic-backups-${name}";
-            CacheDirectory = "restic-backups-${name}";
-            CacheDirectoryMode = "0700";
-          } // optionalAttrs (backup.environmentFile != null) {
-            EnvironmentFile = backup.environmentFile;
-          };
-        } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null) {
-          preStart = ''
-            ${optionalString (backup.initialize) ''
-              ${resticCmd} snapshots || ${resticCmd} init
-            ''}
-            ${optionalString (backup.dynamicFilesFrom != null) ''
-              ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile}
-            ''}
-          '';
-        } // optionalAttrs (backup.dynamicFilesFrom != null) {
-          postStart = ''
-            rm ${filesFromTmpFile}
-          '';
-        })
-      ) config.services.restic.backups;
+      mapAttrs'
+        (name: backup:
+          let
+            extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
+            resticCmd = "${pkgs.restic}/bin/restic${extraOptions}";
+            filesFromTmpFile = "/run/restic-backups-${name}/includes";
+            backupPaths =
+              if (backup.dynamicFilesFrom == null)
+              then if (backup.paths != null) then concatStringsSep " " backup.paths else ""
+              else "--files-from ${filesFromTmpFile}";
+            pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
+              (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts))
+              (resticCmd + " check")
+            ];
+            # Helper functions for rclone remotes
+            rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1;
+            rcloneAttrToOpt = v: "RCLONE_" + toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
+            rcloneAttrToConf = v: "RCLONE_CONFIG_" + toUpper (rcloneRemoteName + "_" + v);
+            toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
+          in
+          nameValuePair "restic-backups-${name}" ({
+            environment = {
+              RESTIC_PASSWORD_FILE = backup.passwordFile;
+              RESTIC_REPOSITORY = backup.repository;
+              RESTIC_REPOSITORY_FILE = backup.repositoryFile;
+            } // optionalAttrs (backup.rcloneOptions != null) (mapAttrs'
+              (name: value:
+                nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
+              )
+              backup.rcloneOptions) // optionalAttrs (backup.rcloneConfigFile != null) {
+              RCLONE_CONFIG = backup.rcloneConfigFile;
+            } // optionalAttrs (backup.rcloneConfig != null) (mapAttrs'
+              (name: value:
+                nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
+              )
+              backup.rcloneConfig);
+            path = [ pkgs.openssh ];
+            restartIfChanged = false;
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ])
+                ++ pruneCmd;
+              User = backup.user;
+              RuntimeDirectory = "restic-backups-${name}";
+              CacheDirectory = "restic-backups-${name}";
+              CacheDirectoryMode = "0700";
+            } // optionalAttrs (backup.environmentFile != null) {
+              EnvironmentFile = backup.environmentFile;
+            };
+          } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null || backup.backupPrepareCommand != null) {
+            preStart = ''
+              ${optionalString (backup.backupPrepareCommand != null) ''
+                ${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
+              ''}
+              ${optionalString (backup.initialize) ''
+                ${resticCmd} snapshots || ${resticCmd} init
+              ''}
+              ${optionalString (backup.dynamicFilesFrom != null) ''
+                ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile}
+              ''}
+            '';
+          } // optionalAttrs (backup.dynamicFilesFrom != null || backup.backupCleanupCommand != null) {
+            postStart = ''
+              ${optionalString (backup.backupCleanupCommand != null) ''
+                ${pkgs.writeScript "backupCleanupCommand" backup.backupCleanupCommand}
+              ''}
+              ${optionalString (backup.dynamicFilesFrom != null) ''
+                rm ${filesFromTmpFile}
+              ''}
+            '';
+          })
+        )
+        config.services.restic.backups;
     systemd.timers =
-      mapAttrs' (name: backup: nameValuePair "restic-backups-${name}" {
-        wantedBy = [ "timers.target" ];
-        timerConfig = backup.timerConfig;
-      }) config.services.restic.backups;
+      mapAttrs'
+        (name: backup: nameValuePair "restic-backups-${name}" {
+          wantedBy = [ "timers.target" ];
+          timerConfig = backup.timerConfig;
+        })
+        config.services.restic.backups;
   };
 }
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 9440382e66a19..87806d48e89f3 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -310,7 +310,11 @@ in
 
           mkdir -m 0700 -p ${baseDir}/queue-runner
           mkdir -m 0750 -p ${baseDir}/build-logs
-          chown hydra-queue-runner:hydra ${baseDir}/queue-runner ${baseDir}/build-logs
+          mkdir -m 0750 -p ${baseDir}/runcommand-logs
+          chown hydra-queue-runner.hydra \
+            ${baseDir}/queue-runner \
+            ${baseDir}/build-logs \
+            ${baseDir}/runcommand-logs
 
           ${optionalString haveLocalDB ''
             if ! [ -e ${baseDir}/.db-created ]; then
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index 1323336d866e9..6459b22519dbe 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -239,7 +239,7 @@ in {
     };
 
     environment.sessionVariables.LD_LIBRARY_PATH =
-      lib.optional cfg.jack.enable "${cfg.package.jack}/lib";
+      lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ];
 
     users = lib.mkIf cfg.systemWide {
       users.pipewire = {
diff --git a/nixos/modules/services/games/asf.nix b/nixos/modules/services/games/asf.nix
index ea2bfd40fffaa..ed1a5544d7a48 100644
--- a/nixos/modules/services/games/asf.nix
+++ b/nixos/modules/services/games/asf.nix
@@ -13,6 +13,8 @@ let
     # is in theory not needed as this is already the default for default builds
     UpdateChannel = 0;
     Headless = true;
+  } // lib.optionalAttrs (cfg.ipcPasswordFile != null) {
+    IPCPassword = "#ipcPassword#";
   });
 
   ipc-config = format.generate "IPC.config" cfg.ipcSettings;
@@ -81,8 +83,7 @@ in
       type = format.type;
       description = ''
         The ASF.json file, all the options are documented <link xlink:href="https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#global-config">here</link>.
-        Do note that `AutoRestart`  and `UpdateChannel` is always to `false`
-respectively `0` because NixOS takes care of updating everything.
+        Do note that `AutoRestart`  and `UpdateChannel` is always to `false` respectively `0` because NixOS takes care of updating everything.
         `Headless` is also always set to `true` because there is no way to provide inputs via a systemd service.
         You should try to keep ASF up to date since upstream does not provide support for anything but the latest version and you're exposing yourself to all kinds of issues - as is outlined <link xlink:href="https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#updateperiod">here</link>.
       '';
@@ -92,6 +93,12 @@ respectively `0` because NixOS takes care of updating everything.
       default = { };
     };
 
+    ipcPasswordFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = "Path to a file containig the password. The file must be readable by the <literal>asf</literal> user/group.";
+    };
+
     ipcSettings = mkOption {
       type = format.type;
       description = ''
@@ -115,14 +122,12 @@ respectively `0` because NixOS takes care of updating everything.
         options = {
           username = mkOption {
             type = types.str;
-            description =
-              "Name of the user to log in. Default is attribute name.";
+            description = "Name of the user to log in. Default is attribute name.";
             default = "";
           };
           passwordFile = mkOption {
             type = types.path;
-            description =
-              "Path to a file containig the password. The file must be readable by the <literal>asf</literal> user/group.";
+            description = "Path to a file containig the password. The file must be readable by the <literal>asf</literal> user/group.";
           };
           enabled = mkOption {
             type = types.bool;
@@ -131,8 +136,7 @@ respectively `0` because NixOS takes care of updating everything.
           };
           settings = mkOption {
             type = types.attrs;
-            description =
-              "Additional settings that are documented <link xlink:href=\"https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#bot-config\">here</link>.";
+            description = "Additional settings that are documented <link xlink:href=\"https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#bot-config\">here</link>.";
             default = { };
           };
         };
@@ -170,14 +174,17 @@ respectively `0` because NixOS takes care of updating everything.
         wantedBy = [ "multi-user.target" ];
 
         serviceConfig = mkMerge [
-          (mkIf (cfg.dataDir == "/var/lib/asf") { StateDirectory = "asf"; })
+          (mkIf (cfg.dataDir == "/var/lib/asf") {
+            StateDirectory = "asf";
+            StateDirectoryMode = "700";
+          })
           {
             User = "asf";
             Group = "asf";
             WorkingDirectory = cfg.dataDir;
             Type = "simple";
-            ExecStart =
-              "${cfg.package}/bin/ArchiSteamFarm --path ${cfg.dataDir} --process-required --no-restart --service --no-config-migrate";
+            ExecStart = "${cfg.package}/bin/ArchiSteamFarm --path ${cfg.dataDir} --process-required --no-restart --service --no-config-migrate";
+            Restart = "always";
 
             # mostly copied from the default systemd service
             PrivateTmp = true;
@@ -202,35 +209,47 @@ respectively `0` because NixOS takes care of updating everything.
           }
         ];
 
-        preStart = ''
-          mkdir -p config
-          rm -f www
-          rm -f config/{*.json,*.config}
-
-          ln -s ${asf-config} config/ASF.json
-
-          ${strings.optionalString (cfg.ipcSettings != {}) ''
-            ln -s ${ipc-config} config/IPC.config
-          ''}
-
-          ln -s ${pkgs.runCommandLocal "ASF-bots" {} ''
-            mkdir -p $out/lib/asf/bots
-            for i in ${strings.concatStringsSep " " (lists.map (x: "${getName x},${x}") (attrsets.mapAttrsToList mkBot cfg.bots))}; do IFS=",";
-              set -- $i
-              ln -s $2 $out/lib/asf/bots/$1
-            done
-          ''}/lib/asf/bots/* config/
-
-          ${strings.optionalString cfg.web-ui.enable ''
-            ln -s ${cfg.web-ui.package}/lib/dist www
-          ''}
-        '';
+        preStart =
+          let
+            createBotsScript = pkgs.runCommandLocal "ASF-bots" { } ''
+              mkdir -p $out
+              # clean potential removed bots
+              rm -rf $out/*.json
+              for i in ${strings.concatStringsSep " " (lists.map (x: "${getName x},${x}") (attrsets.mapAttrsToList mkBot cfg.bots))}; do IFS=",";
+                set -- $i
+                ln -fs $2 $out/$1
+              done
+            '';
+            replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
+          in
+          ''
+            mkdir -p config
+
+            cp --no-preserve=mode ${asf-config} config/ASF.json
+
+            ${optionalString (cfg.ipcPasswordFile != null) ''
+              ${replaceSecretBin} '#ipcPassword#' '${cfg.ipcPasswordFile}' config/ASF.json
+            ''}
+
+            ${optionalString (cfg.ipcSettings != {}) ''
+              ln -fs ${ipc-config} config/IPC.config
+            ''}
+
+            ${optionalString (cfg.ipcSettings != {}) ''
+              ln -fs ${createBotsScript}/* config/
+            ''}
+
+            rm -f www
+            ${optionalString cfg.web-ui.enable ''
+              ln -s ${cfg.web-ui.package}/lib/dist www
+            ''}
+          '';
       };
     };
   };
 
   meta = {
     buildDocsInSandbox = false;
-    maintainers = with maintainers; [ lom ];
+    maintainers = with maintainers; [ lom SuperSandro2000 ];
   };
 }
diff --git a/nixos/modules/services/misc/uhub.nix b/nixos/modules/services/misc/uhub.nix
index 0d0a8c2a4cb81..99774fbb920a0 100644
--- a/nixos/modules/services/misc/uhub.nix
+++ b/nixos/modules/services/misc/uhub.nix
@@ -80,11 +80,12 @@ in {
           tls_enable = cfg.enableTLS;
           file_plugins = pkgs.writeText "uhub-plugins.conf"
             (lib.strings.concatStringsSep "\n" (map ({ plugin, settings }:
-              "plugin ${plugin} ${
-                toString
-                (lib.attrsets.mapAttrsToList (key: value: ''"${key}=${value}"'')
-                  settings)
-              }") cfg.plugins));
+              ''
+                plugin ${plugin} "${
+                  toString
+                  (lib.attrsets.mapAttrsToList (key: value: "${key}=${value}")
+                    settings)
+                }"'') cfg.plugins));
         };
       in {
         name = "uhub/${name}.conf";
@@ -104,6 +105,9 @@ in {
           ExecStart = "${pkg}/bin/uhub -c /etc/uhub/${name}.conf -L";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           DynamicUser = true;
+
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
         };
       };
     }) hubs;
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index f528d18304244..489dd337bb7cc 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -201,6 +201,8 @@ in {
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c /etc/netdata/netdata.conf";
         ExecReload = "${pkgs.util-linux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID";
+        ExecPostStart = ''while [ "$(netdatacli ping)" != pong ]; do sleep 0.5; done'';
+
         TimeoutStopSec = 60;
         Restart = "on-failure";
         # User and group
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index a683c537f05b2..e88daae1fbbac 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -51,7 +51,7 @@ in
 
     services.unifi.openFirewall = mkOption {
       type = types.bool;
-      default = true;
+      default = false;
       description = ''
         Whether or not to open the minimum required ports on the firewall.
 
@@ -85,10 +85,6 @@ in
 
   config = mkIf cfg.enable {
 
-    warnings = optional
-      (options.services.unifi.openFirewall.highestPrio >= (mkOptionDefault null).priority)
-      "The current services.unifi.openFirewall = true default is deprecated and will change to false in 22.11. Set it explicitly to silence this warning.";
-
     users.users.unifi = {
       isSystemUser = true;
       group = "unifi";
diff --git a/nixos/modules/services/security/infnoise.nix b/nixos/modules/services/security/infnoise.nix
new file mode 100644
index 0000000000000..4fb8adaf33f89
--- /dev/null
+++ b/nixos/modules/services/security/infnoise.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.infnoise;
+in {
+  options = {
+    services.infnoise = {
+      enable = mkEnableOption "the Infinite Noise TRNG driver";
+
+      fillDevRandom = mkOption {
+        description = ''
+          Whether to run the infnoise driver as a daemon to refill /dev/random.
+
+          If disabled, you can use the `infnoise` command-line tool to
+          manually obtain randomness.
+        '';
+        type = types.bool;
+        default = true;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.infnoise ];
+
+    services.udev.extraRules = ''
+      SUBSYSTEM=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", SYMLINK+="infnoise", TAG+="systemd", GROUP="dialout", MODE="0664", ENV{SYSTEMD_WANTS}="infnoise.service"
+    '';
+
+    systemd.services.infnoise = mkIf cfg.fillDevRandom {
+      description = "Infinite Noise TRNG driver";
+
+      bindsTo = [ "dev-infnoise.device" ];
+      after = [ "dev-infnoise.device" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.infnoise}/bin/infnoise --dev-random --debug";
+        Restart = "always";
+        User = "infnoise";
+        DynamicUser = true;
+        SupplementaryGroups = [ "dialout" ];
+        DeviceAllow = [ "/dev/infnoise" ];
+        DevicePolicy = "closed";
+        PrivateNetwork = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true; # only reads entropy pool size and watermark
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index e195e6e6e8240..e6b6aa273e7fb 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -209,7 +209,7 @@ in {
 
       port = lib.mkOption {
         type = lib.types.nullOr lib.types.port;
-        default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 6379;
+        default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 31638;
         defaultText = lib.literalExpression ''
           if config.${opt.redis.createLocally} && config.${opt.redis.enableUnixSocket}
           then null
@@ -344,7 +344,7 @@ in {
           };
         };
       }
-      (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis/redis.sock"; }; })
+      (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis-peertube/redis.sock"; }; })
     ];
 
     systemd.tmpfiles.rules = [
@@ -441,13 +441,17 @@ in {
       enable = true;
     };
 
-    services.redis = lib.mkMerge [
+    services.redis.servers.peertube = lib.mkMerge [
       (lib.mkIf cfg.redis.createLocally {
         enable = true;
       })
+      (lib.mkIf (cfg.redis.createLocally && !cfg.redis.enableUnixSocket) {
+        bind = "127.0.0.1";
+        port = cfg.redis.port;
+      })
       (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
-        unixSocket = "/run/redis/redis.sock";
-        unixSocketPerm = 770;
+        unixSocket = "/run/redis-peertube/redis.sock";
+        unixSocketPerm = 660;
       })
     ];
 
@@ -465,7 +469,7 @@ in {
         };
       })
       (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs-16_x pkgs.yarn ])
-      (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis" ];})
+      (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
     ];
 
     users.groups = lib.optionalAttrs (cfg.group == "peertube") {
diff --git a/nixos/modules/services/x11/desktop-managers/phosh.nix b/nixos/modules/services/x11/desktop-managers/phosh.nix
index 4bf78fa16e7d9..5efe645d8aa24 100644
--- a/nixos/modules/services/x11/desktop-managers/phosh.nix
+++ b/nixos/modules/services/x11/desktop-managers/phosh.nix
@@ -78,7 +78,13 @@ let
         description = ''
           Display scaling factor.
         '';
-        type = types.nullOr types.ints.unsigned;
+        type = types.nullOr (
+          types.addCheck
+          (types.either types.int types.float)
+          (x : x > 0)
+        ) // {
+          description = "null or positive integer or float";
+        };
         default = null;
         example = 2;
       };
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index b0f160c1dbf95..66fdc61d28357 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -219,14 +219,15 @@ let
                     cidr = "${route.address}/${toString route.prefixLength}";
                     via = optionalString (route.via != null) ''via "${route.via}"'';
                     options = concatStrings (mapAttrsToList (name: val: "${name} ${val} ") route.options);
+                    type = toString route.type;
                   in
                   ''
                      echo "${cidr}" >> $state
                      echo -n "adding route ${cidr}... "
-                     if out=$(ip route add "${cidr}" ${options} ${via} dev "${i.name}" proto static 2>&1); then
+                     if out=$(ip route add ${type} "${cidr}" ${options} ${via} dev "${i.name}" proto static 2>&1); then
                        echo "done"
                      elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
-                       echo "'ip route add "${cidr}" ${options} ${via} dev "${i.name}"' failed: $out"
+                       echo "'ip route add ${type} "${cidr}" ${options} ${via} dev "${i.name}"' failed: $out"
                        exit 1
                      fi
                   ''
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index 110e84494a3dc..80808e0c08fa5 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -142,6 +142,9 @@ in
                 optionalAttrs (route.via != null) {
                   Gateway = route.via;
                 } //
+                optionalAttrs (route.type != null) {
+                  Type = route.type;
+                } //
                 optionalAttrs (route.options ? onlink) {
                   GatewayOnLink = true;
                 } //
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index d56159f15960d..07bccf98f407f 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -90,6 +90,22 @@ let
         '';
       };
 
+      type = mkOption {
+        type = types.nullOr (types.enum [
+          "unicast" "local" "broadcast" "multicast"
+        ]);
+        default = null;
+        description = ''
+          Type of the route.  See the <literal>Route types</literal> section
+          in the <literal>ip-route(8)</literal> manual page for the details.
+
+          Note that <literal>prohibit</literal>, <literal>blackhole</literal>,
+          <literal>unreachable</literal>, and <literal>throw</literal> cannot
+          be configured per device, so they are not available here. Similarly,
+          <literal>nat</literal> hasn't been supported since kernel 2.6.
+        '';
+      };
+
       via = mkOption {
         type = types.nullOr types.str;
         default = null;
diff --git a/nixos/modules/virtualisation/docker-rootless.nix b/nixos/modules/virtualisation/docker-rootless.nix
index d371f67ecdc84..b814fa1c4358c 100644
--- a/nixos/modules/virtualisation/docker-rootless.nix
+++ b/nixos/modules/virtualisation/docker-rootless.nix
@@ -51,7 +51,6 @@ in
       default = pkgs.docker;
       defaultText = literalExpression "pkgs.docker";
       type = types.package;
-      example = literalExpression "pkgs.docker-edge";
       description = ''
         Docker package to be used in the module.
       '';
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index a69cbe55c7845..c6eca4d6ed584 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -155,7 +155,6 @@ in
       default = pkgs.docker;
       defaultText = literalExpression "pkgs.docker";
       type = types.package;
-      example = literalExpression "pkgs.docker-edge";
       description = ''
         Docker package to be used in the module.
       '';
diff --git a/nixos/release.nix b/nixos/release.nix
index 0df443dd204cb..e0d782bcaec3d 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -17,6 +17,7 @@ let
   # Run the tests for each platform.  You can run a test by doing
   # e.g. ‘nix-build -A tests.login.x86_64-linux’, or equivalently,
   # ‘nix-build tests/login.nix -A result’.
+  # See also nixosTests in pkgs/top-level/all-packages.nix
   allTestsForSystem = system:
     import ./tests/all-tests.nix {
       inherit system;
@@ -24,7 +25,19 @@ let
       callTest = t: {
         ${system} = hydraJob t.test;
       };
+    } // {
+      # for typechecking of the scripts and evaluation of
+      # the nodes, without running VMs.
+      allDrivers =
+        import ./tests/all-tests.nix {
+        inherit system;
+        pkgs = import ./.. { inherit system; };
+        callTest = t: {
+          ${system} = hydraJob t.test.driver;
+        };
+      };
     };
+
   allTests =
     foldAttrs recursiveUpdate {} (map allTestsForSystem supportedSystems);
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index e61798676a0de..0f75548ff6fda 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -26,8 +26,8 @@ let
     featureFlags.minimalModules = {};
   };
   evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; };
-in
-{
+
+in {
   _3proxy = handleTest ./3proxy.nix {};
   acme = handleTest ./acme.nix {};
   adguardhome = handleTest ./adguardhome.nix {};
@@ -123,7 +123,6 @@ in
   doas = handleTest ./doas.nix {};
   docker = handleTestOn ["x86_64-linux"] ./docker.nix {};
   docker-rootless = handleTestOn ["x86_64-linux"] ./docker-rootless.nix {};
-  docker-edge = handleTestOn ["x86_64-linux"] ./docker-edge.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 {};
@@ -152,6 +151,7 @@ in
   etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
   etebase-server = handleTest ./etebase-server.nix {};
   etesync-dav = handleTest ./etesync-dav.nix {};
+  extra-python-packages = handleTest ./extra-python-packages.nix {};
   fancontrol = handleTest ./fancontrol.nix {};
   fcitx = handleTest ./fcitx {};
   fenics = handleTest ./fenics.nix {};
diff --git a/nixos/tests/docker-edge.nix b/nixos/tests/docker-edge.nix
deleted file mode 100644
index c6a1a08301890..0000000000000
--- a/nixos/tests/docker-edge.nix
+++ /dev/null
@@ -1,49 +0,0 @@
-# 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.package = pkgs.docker-edge;
-
-          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-edge.version} | wc -l) = "4" ]')
-  '';
-})
diff --git a/nixos/tests/ecryptfs.nix b/nixos/tests/ecryptfs.nix
index e3cfb2ed998c2..1c67d307a00e8 100644
--- a/nixos/tests/ecryptfs.nix
+++ b/nixos/tests/ecryptfs.nix
@@ -11,16 +11,16 @@ import ./make-test-python.nix ({ ... }:
 
   testScript = ''
     def login_as_alice():
-        machine.wait_until_tty_matches(1, "login: ")
+        machine.wait_until_tty_matches("1", "login: ")
         machine.send_chars("alice\n")
-        machine.wait_until_tty_matches(1, "Password: ")
+        machine.wait_until_tty_matches("1", "Password: ")
         machine.send_chars("foobar\n")
-        machine.wait_until_tty_matches(1, "alice\@machine")
+        machine.wait_until_tty_matches("1", "alice\@machine")
 
 
     def logout():
         machine.send_chars("logout\n")
-        machine.wait_until_tty_matches(1, "login: ")
+        machine.wait_until_tty_matches("1", "login: ")
 
 
     machine.wait_for_unit("default.target")
@@ -36,7 +36,7 @@ import ./make-test-python.nix ({ ... }:
     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: ")
+        machine.wait_until_tty_matches("1", "login: ")
 
     # Why do I need to do this??
     machine.succeed("su alice -c ecryptfs-umount-private || true")
diff --git a/nixos/tests/extra-python-packages.nix b/nixos/tests/extra-python-packages.nix
new file mode 100644
index 0000000000000..7a48077cf98bc
--- /dev/null
+++ b/nixos/tests/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/nixos/tests/firefox.nix b/nixos/tests/firefox.nix
index c773368a3e60a..63ccc6efb5bb4 100644
--- a/nixos/tests/firefox.nix
+++ b/nixos/tests/firefox.nix
@@ -54,7 +54,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
 
 
       @contextmanager
-      def audio_recording(machine: Machine) -> None:
+      def record_audio(machine: Machine):
           """
           Perform actions while recording the
           machine audio output.
@@ -64,7 +64,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
           machine.systemctl("stop audio-recorder")
 
 
-      def wait_for_sound(machine: Machine) -> None:
+      def wait_for_sound(machine: Machine):
           """
           Wait until any sound has been emitted.
           """
@@ -94,7 +94,7 @@ import ./make-test-python.nix ({ pkgs, firefoxPackage, ... }: {
           machine.sleep(40)
 
       with subtest("Check whether Firefox can play sound"):
-          with audio_recording(machine):
+          with record_audio(machine):
               machine.succeed(
                   "firefox file://${pkgs.sound-theme-freedesktop}/share/sounds/freedesktop/stereo/phone-incoming-call.oga >&2 &"
               )
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 10f9cb05c9cb1..f7b9d283e4572 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -111,6 +111,7 @@ in {
     pattern = re.compile(r"path=(?P<path>[\/a-z0-9-.]+)\/bin\/hass")
     response = hass.execute("systemctl show -p ExecStart home-assistant.service")[1]
     match = pattern.search(response)
+    assert match
     package = match.group('path')
 
     hass.wait_for_unit("home-assistant.service")
diff --git a/nixos/tests/ihatemoney/default.nix b/nixos/tests/ihatemoney/default.nix
index cd5f073343daa..894a97d43d35e 100644
--- a/nixos/tests/ihatemoney/default.nix
+++ b/nixos/tests/ihatemoney/default.nix
@@ -32,14 +32,7 @@ let
         };
       };
       # ihatemoney needs a local smtp server otherwise project creation just crashes
-      services.opensmtpd = {
-        enable = true;
-        serverConfiguration = ''
-          listen on lo
-          action foo relay
-          match from any for any action foo
-        '';
-      };
+      services.postfix.enable = true;
     };
     testScript = ''
       machine.wait_for_open_port(8000)
diff --git a/nixos/tests/login.nix b/nixos/tests/login.nix
index 0d6f81b172191..2cff38d20059d 100644
--- a/nixos/tests/login.nix
+++ b/nixos/tests/login.nix
@@ -29,11 +29,11 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
           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.wait_until_tty_matches("2", "login: ")
           machine.send_chars("alice\n")
-          machine.wait_until_tty_matches(2, "login: alice")
+          machine.wait_until_tty_matches("2", "login: alice")
           machine.wait_until_succeeds("pgrep login")
-          machine.wait_until_tty_matches(2, "Password: ")
+          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")
diff --git a/nixos/tests/matrix/mjolnir.nix b/nixos/tests/matrix/mjolnir.nix
index cb843e2e9e3e8..3864f0ff2bb6d 100644
--- a/nixos/tests/matrix/mjolnir.nix
+++ b/nixos/tests/matrix/mjolnir.nix
@@ -45,6 +45,7 @@ import ../make-test-python.nix (
             enable_registration = true;
             enable_registration_without_verification = true;
             registration_shared_secret = "supersecret-registration";
+            enable_registration_without_verification = true;
 
             listeners = [ {
               # The default but tls=false
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 2cc1e9b0942ca..1fe1229f24a4a 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -77,12 +77,14 @@ let
   testCases = {
     loopback = {
       name = "Loopback";
-      machine.networking.useDHCP = false;
-      machine.networking.useNetworkd = networkd;
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        networking.useDHCP = false;
+        networking.useNetworkd = networkd;
+      };
       testScript = ''
         start_all()
-        machine.wait_for_unit("network.target")
-        loopback_addresses = machine.succeed("ip addr show lo")
+        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
       '';
@@ -139,6 +141,25 @@ let
               client.wait_until_succeeds("ping -c 1 192.168.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;
diff --git a/nixos/tests/nitter.nix b/nixos/tests/nitter.nix
index 0e1a6d150f38e..8bc55ba8c69fc 100644
--- a/nixos/tests/nitter.nix
+++ b/nixos/tests/nitter.nix
@@ -12,7 +12,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
 
   testScript = ''
     machine.wait_for_unit("nitter.service")
-    machine.wait_for_open_port("80")
+    machine.wait_for_open_port(80)
     machine.succeed("curl --fail http://localhost:80/")
   '';
 })
diff --git a/nixos/tests/pam/pam-oath-login.nix b/nixos/tests/pam/pam-oath-login.nix
index c532e81e674d7..dd6ef4a0abcb8 100644
--- a/nixos/tests/pam/pam-oath-login.nix
+++ b/nixos/tests/pam/pam-oath-login.nix
@@ -77,28 +77,28 @@ in
     machine.screenshot("postboot")
 
     with subtest("Invalid password"):
-        switch_to_tty(2)
-        enter_user_alice(2)
+        switch_to_tty("2")
+        enter_user_alice("2")
 
         machine.send_chars("${oathSnakeOilPassword1}\n")
-        machine.wait_until_tty_matches(2, "Password: ")
+        machine.wait_until_tty_matches("2", "Password: ")
         machine.send_chars("blorg\n")
-        machine.wait_until_tty_matches(2, "Login incorrect")
+        machine.wait_until_tty_matches("2", "Login incorrect")
 
     with subtest("Invalid oath token"):
-        switch_to_tty(3)
-        enter_user_alice(3)
+        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:")
+        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)
+        switch_to_tty("4")
+        enter_user_alice("4")
 
         machine.send_chars("${oathSnakeOilPassword2}\n")
-        machine.wait_until_tty_matches(4, "Password: ")
+        machine.wait_until_tty_matches("4", "Password: ")
         machine.send_chars("${alicePassword}\n")
 
         machine.wait_until_succeeds("pgrep -u alice bash")
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 16979eab82170..7523d5e5ed5da 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -1,96 +1,119 @@
 import ./make-test-python.nix (
   { pkgs, ... }:
 
-    let
-      password = "some_password";
-      repository = "/tmp/restic-backup";
-      rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
+  let
+    password = "some_password";
+    repository = "/tmp/restic-backup";
+    repositoryFile = "${pkgs.writeText "repositoryFile" "/tmp/restic-backup-from-file"}";
+    rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
 
-      passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
-      initialize = true;
-      paths = [ "/opt" ];
-      pruneOpts = [
-        "--keep-daily 2"
-        "--keep-weekly 1"
-        "--keep-monthly 1"
-        "--keep-yearly 99"
-      ];
-    in
-      {
-        name = "restic";
+    backupPrepareCommand = ''
+      touch /opt/backupPrepareCommand
+      test ! -e /opt/backupCleanupCommand
+    '';
 
-        meta = with pkgs.lib.maintainers; {
-          maintainers = [ bbigras i077 ];
-        };
+    backupCleanupCommand = ''
+      rm /opt/backupPrepareCommand
+      touch /opt/backupCleanupCommand
+    '';
 
-        nodes = {
-          server =
-            { pkgs, ... }:
-              {
-                services.restic.backups = {
-                  remotebackup = {
-                    inherit repository passwordFile initialize paths pruneOpts;
-                  };
-                  rclonebackup = {
-                    repository = rcloneRepository;
-                    rcloneConfig = {
-                      type = "local";
-                      one_file_system = true;
-                    };
+    passwordFile = "${pkgs.writeText "password" "correcthorsebatterystaple"}";
+    initialize = true;
+    paths = [ "/opt" ];
+    pruneOpts = [
+      "--keep-daily 2"
+      "--keep-weekly 1"
+      "--keep-monthly 1"
+      "--keep-yearly 99"
+    ];
+  in
+  {
+    name = "restic";
 
-                    # This gets overridden by rcloneConfig.type
-                    rcloneConfigFile = pkgs.writeText "rclone.conf" ''
-                      [local]
-                      type=ftp
-                    '';
-                    inherit passwordFile initialize paths pruneOpts;
-                  };
-                  remoteprune = {
-                    inherit repository passwordFile;
-                    pruneOpts = [ "--keep-last 1" ];
-                  };
-                };
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bbigras i077 ];
+    };
 
-                environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
+    nodes = {
+      server =
+        { pkgs, ... }:
+        {
+          services.restic.backups = {
+            remotebackup = {
+              inherit repository passwordFile initialize paths pruneOpts backupPrepareCommand backupCleanupCommand;
+            };
+            remotebackup-from-file = {
+              inherit repositoryFile passwordFile initialize paths pruneOpts;
+            };
+            rclonebackup = {
+              repository = rcloneRepository;
+              rcloneConfig = {
+                type = "local";
+                one_file_system = true;
               };
+
+              # This gets overridden by rcloneConfig.type
+              rcloneConfigFile = pkgs.writeText "rclone.conf" ''
+                [local]
+                type=ftp
+              '';
+              inherit passwordFile initialize paths pruneOpts;
+            };
+            remoteprune = {
+              inherit repository passwordFile;
+              pruneOpts = [ "--keep-last 1" ];
+            };
+          };
+
+          environment.sessionVariables.RCLONE_CONFIG_LOCAL_TYPE = "local";
         };
+    };
 
-        testScript = ''
-          server.start()
-          server.wait_for_unit("dbus.socket")
-          server.fail(
-              "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots",
-              "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
-          )
-          server.succeed(
-              "mkdir -p /opt",
-              "touch /opt/some_file",
-              "mkdir -p /tmp/restic-rclone-backup",
-              "timedatectl set-time '2016-12-13 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
-              '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
-              "timedatectl set-time '2017-12-13 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              "timedatectl set-time '2018-12-13 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              "timedatectl set-time '2018-12-14 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              "timedatectl set-time '2018-12-15 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              "timedatectl set-time '2018-12-16 13:45'",
-              "systemctl start restic-backups-remotebackup.service",
-              "systemctl start restic-backups-rclonebackup.service",
-              '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
-              '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
-              "systemctl start restic-backups-remoteprune.service",
-              '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
-          )
-        '';
-      }
+    testScript = ''
+      server.start()
+      server.wait_for_unit("dbus.socket")
+      server.fail(
+          "${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots",
+          '${pkgs.restic}/bin/restic --repository-file ${repositoryFile} -p ${passwordFile} snapshots"',
+          "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
+      )
+      server.succeed(
+          "mkdir -p /opt",
+          "touch /opt/some_file",
+          "mkdir -p /tmp/restic-rclone-backup",
+          "timedatectl set-time '2016-12-13 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-remotebackup-from-file.service",
+          "systemctl start restic-backups-rclonebackup.service",
+          '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+          '${pkgs.restic}/bin/restic --repository-file ${repositoryFile} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+          '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+          "timedatectl set-time '2017-12-13 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+          "timedatectl set-time '2018-12-13 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+          "timedatectl set-time '2018-12-14 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+          "timedatectl set-time '2018-12-15 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+          "timedatectl set-time '2018-12-16 13:45'",
+          "systemctl start restic-backups-remotebackup.service",
+          "rm /opt/backupCleanupCommand",
+          "systemctl start restic-backups-rclonebackup.service",
+          '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
+          '${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots -c | grep -e "^4 snapshot"',
+          "systemctl start restic-backups-remoteprune.service",
+          '${pkgs.restic}/bin/restic -r ${repository} -p ${passwordFile} snapshots -c | grep -e "^1 snapshot"',
+      )
+    '';
+  }
 )
diff --git a/nixos/tests/shadow.nix b/nixos/tests/shadow.nix
index dd2a575b1935a..50a9f71246469 100644
--- a/nixos/tests/shadow.nix
+++ b/nixos/tests/shadow.nix
@@ -39,9 +39,9 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         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.wait_until_tty_matches("2", "login: ")
         shadow.send_chars("emma\n")
-        shadow.wait_until_tty_matches(2, "login: emma")
+        shadow.wait_until_tty_matches("2", "login: emma")
         shadow.wait_until_succeeds("pgrep login")
         shadow.sleep(2)
         shadow.send_chars("${password1}\n")
@@ -63,9 +63,9 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         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.wait_until_tty_matches("3", "login: ")
         shadow.send_chars("emma\n")
-        shadow.wait_until_tty_matches(3, "login: emma")
+        shadow.wait_until_tty_matches("3", "login: emma")
         shadow.wait_until_succeeds("pgrep login")
         shadow.sleep(2)
         shadow.send_chars("${password1}\n")
@@ -81,16 +81,16 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         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.wait_until_tty_matches("4", "login: ")
         shadow.send_chars("emma\n")
-        shadow.wait_until_tty_matches(4, "login: emma")
+        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.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_tty_matches("4", "login: emma")
         shadow.wait_until_succeeds("pgrep login")
         shadow.sleep(2)
         shadow.send_chars("${password3}\n")
@@ -109,11 +109,11 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
         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.wait_until_tty_matches("5", "login: ")
         shadow.send_chars("layla\n")
-        shadow.wait_until_tty_matches(5, "login: layla")
+        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:")
+        shadow.wait_until_tty_matches("5", "login:")
   '';
 })
diff --git a/nixos/tests/sway.nix b/nixos/tests/sway.nix
index 8f95f2a030d1b..52e2c7c99ec4b 100644
--- a/nixos/tests/sway.nix
+++ b/nixos/tests/sway.nix
@@ -4,6 +4,12 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     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 ];
diff --git a/nixos/tests/uptermd.nix b/nixos/tests/uptermd.nix
index d504ef0641916..429e3c9dd5ff3 100644
--- a/nixos/tests/uptermd.nix
+++ b/nixos/tests/uptermd.nix
@@ -42,7 +42,7 @@ in
 
     client1.wait_for_unit("multi-user.target")
     client1.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
-    client1.wait_until_tty_matches(1, "login: ")
+    client1.wait_until_tty_matches("1", "login: ")
     client1.send_chars("root\n")
     client1.wait_until_succeeds("pgrep -u root bash")
 
diff --git a/nixos/tests/user-activation-scripts.nix b/nixos/tests/user-activation-scripts.nix
index 9345735781873..5df072ce0508a 100644
--- a/nixos/tests/user-activation-scripts.nix
+++ b/nixos/tests/user-activation-scripts.nix
@@ -19,9 +19,9 @@ import ./make-test-python.nix ({ lib, ... }: {
 
     machine.wait_for_unit("multi-user.target")
     machine.wait_for_unit("getty@tty1.service")
-    machine.wait_until_tty_matches(1, "login: ")
+    machine.wait_until_tty_matches("1", "login: ")
     machine.send_chars("alice\n")
-    machine.wait_until_tty_matches(1, "Password: ")
+    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")
diff --git a/nixos/tests/user-home-mode.nix b/nixos/tests/user-home-mode.nix
index 1366d102a99b3..070cb0b75cc9d 100644
--- a/nixos/tests/user-home-mode.nix
+++ b/nixos/tests/user-home-mode.nix
@@ -17,9 +17,9 @@ import ./make-test-python.nix ({ lib, ... }: {
   testScript = ''
     machine.wait_for_unit("multi-user.target")
     machine.wait_for_unit("getty@tty1.service")
-    machine.wait_until_tty_matches(1, "login: ")
+    machine.wait_until_tty_matches("1", "login: ")
     machine.send_chars("alice\n")
-    machine.wait_until_tty_matches(1, "Password: ")
+    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/nixos/tests/web-apps/peertube.nix b/nixos/tests/web-apps/peertube.nix
index d42b4e3d677bb..ecc45bff2e2ca 100644
--- a/nixos/tests/web-apps/peertube.nix
+++ b/nixos/tests/web-apps/peertube.nix
@@ -11,7 +11,7 @@ import ../make-test-python.nix ({pkgs, ...}:
             { address = "192.168.2.10"; prefixLength = 24; }
           ];
         };
-        firewall.allowedTCPPorts = [ 5432 6379 ];
+        firewall.allowedTCPPorts = [ 5432 31638 ];
       };
 
       services.postgresql = {
@@ -34,7 +34,7 @@ import ../make-test-python.nix ({pkgs, ...}:
         enable = true;
         bind = "0.0.0.0";
         requirePass = "turrQfaQwnanGbcsdhxy";
-        port = 6379;
+        port = 31638;
       };
     };
 
@@ -76,6 +76,7 @@ import ../make-test-python.nix ({pkgs, ...}:
 
         redis = {
           host = "192.168.2.10";
+          port = 31638;
           passwordFile = "/etc/peertube/password-redis-db";
         };
 
@@ -113,7 +114,7 @@ import ../make-test-python.nix ({pkgs, ...}:
     database.wait_for_unit("redis-peertube.service")
 
     database.wait_for_open_port(5432)
-    database.wait_for_open_port(6379)
+    database.wait_for_open_port(31638)
 
     server.wait_for_unit("peertube.service")
     server.wait_for_open_port(9000)
diff --git a/nixos/tests/zsh-history.nix b/nixos/tests/zsh-history.nix
index 355687798406b..64f32a07e2158 100644
--- a/nixos/tests/zsh-history.nix
+++ b/nixos/tests/zsh-history.nix
@@ -21,13 +21,13 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     default.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
 
     # Login
-    default.wait_until_tty_matches(1, "login: ")
+    default.wait_until_tty_matches("1", "login: ")
     default.send_chars("root\n")
-    default.wait_until_tty_matches(1, r"\nroot@default\b")
+    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")
+    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")