about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/configuration/sshfs-file-systems.section.md4
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md34
-rw-r--r--nixos/lib/systemd-lib.nix5
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py2
-rw-r--r--nixos/lib/testing/meta.nix7
-rw-r--r--nixos/modules/image/repart-image.nix20
-rw-r--r--nixos/modules/image/repart.nix38
-rw-r--r--nixos/modules/module-list.nix2
-rw-r--r--nixos/modules/programs/fzf.nix48
-rw-r--r--nixos/modules/programs/oddjobd.nix22
-rw-r--r--nixos/modules/programs/steam.nix2
-rw-r--r--nixos/modules/programs/wayland/hyprland.nix22
-rw-r--r--nixos/modules/programs/xss-lock.nix1
-rw-r--r--nixos/modules/services/backup/borgbackup.nix3
-rw-r--r--nixos/modules/services/desktop-managers/plasma6.nix2
-rw-r--r--nixos/modules/services/matrix/mautrix-meta.nix2
-rw-r--r--nixos/modules/services/misc/ollama.nix20
-rw-r--r--nixos/modules/services/monitoring/nezha-agent.nix103
-rw-r--r--nixos/modules/services/monitoring/thanos.nix8
-rw-r--r--nixos/modules/services/networking/bind.nix2
-rw-r--r--nixos/modules/services/networking/mihomo.nix8
-rw-r--r--nixos/modules/services/networking/soju.nix24
-rw-r--r--nixos/modules/services/web-apps/healthchecks.nix3
-rw-r--r--nixos/modules/services/web-apps/hledger-web.nix45
-rw-r--r--nixos/modules/services/web-apps/kavita.nix4
-rw-r--r--nixos/modules/services/web-apps/outline.nix2
-rw-r--r--nixos/modules/services/web-apps/silverbullet.nix123
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix16
-rw-r--r--nixos/modules/system/boot/systemd/repart.nix6
-rw-r--r--nixos/modules/tasks/filesystems/bcachefs.nix19
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix7
-rw-r--r--nixos/modules/testing/test-instrumentation.nix2
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix5
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix9
-rw-r--r--nixos/tests/all-tests.nix4
-rw-r--r--nixos/tests/containers-require-bind-mounts.nix35
-rw-r--r--nixos/tests/docker-tools.nix8
-rw-r--r--nixos/tests/hledger-web.nix2
-rw-r--r--nixos/tests/installer-systemd-stage-1.nix2
-rw-r--r--nixos/tests/installer.nix403
-rw-r--r--nixos/tests/oddjobd.nix23
-rw-r--r--nixos/tests/pantheon.nix2
-rw-r--r--nixos/tests/silverbullet.nix47
-rw-r--r--nixos/tests/soju.nix31
-rw-r--r--nixos/tests/switch-test.nix16
47 files changed, 865 insertions, 332 deletions
diff --git a/nixos/doc/manual/configuration/sshfs-file-systems.section.md b/nixos/doc/manual/configuration/sshfs-file-systems.section.md
index e2e37454b7ead..32b4aac78304d 100644
--- a/nixos/doc/manual/configuration/sshfs-file-systems.section.md
+++ b/nixos/doc/manual/configuration/sshfs-file-systems.section.md
@@ -26,8 +26,8 @@ To create a new key without a passphrase you can do:
 ```ShellSession
 $ ssh-keygen -t ed25519 -P '' -f example-key
 Generating public/private ed25519 key pair.
-Your identification has been saved in test-key
-Your public key has been saved in test-key.pub
+Your identification has been saved in example-key
+Your public key has been saved in example-key.pub
 The key fingerprint is:
 SHA256:yjxl3UbTn31fLWeyLYTAKYJPRmzknjQZoyG8gSNEoIE my-user@workstation
 ```
diff --git a/nixos/doc/manual/release-notes/rl-1909.section.md b/nixos/doc/manual/release-notes/rl-1909.section.md
index 2bd04f8dd40a3..49fc98c313ac3 100644
--- a/nixos/doc/manual/release-notes/rl-1909.section.md
+++ b/nixos/doc/manual/release-notes/rl-1909.section.md
@@ -230,7 +230,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `documentation` module gained an option named `documentation.nixos.includeAllModules` which makes the generated configuration.nix 5 manual page include all options from all NixOS modules included in a given `configuration.nix` configuration file. Currently, it is set to `false` by default as enabling it frequently prevents evaluation. But the plan is to eventually have it set to `true` by default. Please set it to `true` now in your `configuration.nix` and fix all the bugs it uncovers.
 
-- The `vlc` package gained support for Chromecast streaming, enabled by default. TCP port 8010 must be open for it to work, so something like `networking.firewall.allowedTCPPorts = [ 8010 ];` may be required in your configuration. Also consider enabling [ Accelerated Video Playback](https://nixos.wiki/wiki/Accelerated_Video_Playback) for better transcoding performance.
+- The `vlc` package gained support for Chromecast streaming, enabled by default. TCP port 8010 must be open for it to work, so something like `networking.firewall.allowedTCPPorts = [ 8010 ];` may be required in your configuration. Also consider enabling [ Accelerated Video Playback](https://wiki.nixos.org/wiki/Accelerated_Video_Playback) for better transcoding performance.
 
 - The following changes apply if the `stateVersion` is changed to 19.09 or higher. For `stateVersion = "19.03"` or lower the old behavior is preserved.
 
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index ce874a6e0b2d6..031442940b9e3 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -79,7 +79,7 @@ In addition to numerous new and updated packages, this release has the following
 
 - [frigate](https://frigate.video), an open source NVR built around real-time AI object detection. Available as [services.frigate](#opt-services.frigate.enable).
 
-- [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
+- [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.enable).
 
 - [gemstash](https://github.com/rubygems/gemstash), a RubyGems.org cache and private gem server. Available as [services.gemstash](#opt-services.gemstash.enable).
 
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index 085d3f7fa956b..065b0101691f5 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -102,7 +102,7 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
 
 - [ollama](https://ollama.ai), server for running large language models locally.
 
-- [Mihomo](https://github.com/MetaCubeX/mihomo), a rule-based proxy in Go. Available as [services.mihomo.enable](#opt-services.mihomo.enable).
+- [Mihomo](https://github.com/MetaCubeX/mihomo/tree/Alpha), a rule-based proxy in Go. Available as [services.mihomo.enable](#opt-services.mihomo.enable).
 
 - [hebbot](https://github.com/haecker-felix/hebbot), a Matrix bot to generate "This Week in X" like blog posts. Available as [services.hebbot](#opt-services.hebbot.enable).
 
@@ -180,6 +180,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `unrar` was updated to v7. See [changelog](https://www.rarlab.com/unrar7notes.htm) for more information.
 
+- `git-town` was updated from version `11` to `13`. See the [changelog](https://github.com/git-town/git-town/blob/main/CHANGELOG.md#1300-2024-03-22) for breaking changes.
+
 - `k9s` was updated to v0.31. There have been various breaking changes in the config file format,
   check out the changelog of [v0.29](https://github.com/derailed/k9s/releases/tag/v0.29.0),
   [v0.30](https://github.com/derailed/k9s/releases/tag/v0.30.0) and
@@ -237,6 +239,16 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - The legacy and long deprecated systemd target `network-interfaces.target` has been removed. Use `network.target` instead.
 
+- `azure-cli` now has extension support. For example, to install the `aks-preview` extension, use
+
+  ```nix
+  environment.systemPackages = [
+    (azure-cli.withExtensions [ azure-cli.extensions.aks-preview ]);
+  ];
+  ```
+  To make the `azure-cli` immutable and prevent clashes in case `azure-cli` is also installed via other package managers, some configuration files were moved into the derivation.
+  This can be disabled by overriding `withImmutableConfig = false` when building `azure-cli`.
+
 - `services.frp.settings` now generates the frp configuration file in TOML format as [recommended by upstream](https://github.com/fatedier/frp#configuration-files), instead of the legacy INI format. This has also introduced other changes in the configuration file structure and options.
   - The `settings.common` section in the configuration is no longer valid and all the options form inside it now goes directly under `settings`.
   - The `_` separating words in the configuration options is removed so the options are now in camel case. For example: `server_addr` becomes `serverAddr`, `server_port` becomes `serverPort` etc.
@@ -289,14 +301,25 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - The `cudaPackages` package scope has been updated to `cudaPackages_12`.
 
+- The `halloy` package was updated past 2024.5 which introduced a breaking change by switching the config format from YAML to TOML. See https://github.com/squidowl/halloy/releases/tag/2024.5 for details.
+
 - Ada packages (libraries and tools) have been moved into the `gnatPackages` scope. `gnatPackages` uses the default GNAT compiler, `gnat12Packages` and `gnat13Packages` use the respective matching compiler version.
 
 - `spark2014` has been renamed to `gnatprove`. A version of `gnatprove` matching different GNAT versions is available from the different `gnatPackages` sets.
 
 - `services.resolved.fallbackDns` can now be used to disable the upstream fallback servers entirely by setting it to an empty list. To get the previous behaviour of the upstream defaults set it to null, the new default, instead.
 
+- `services.hledger-web.capabilities` options has been replaced by a new option `services.hledger-web.allow`.
+
+  - `allow = "view"` means `capabilities = { view = true; }`;
+  - `allow = "add"` means `capabilities = { view = true; add = true; }`;
+  - `allow = "edit"` means `capabilities = { view = true; add = true; edit = true }`;
+  - `allow = "sandstorm"` reads permissions from the `X-Sandstorm-Permissions` request header.
+
 - `xxd` has been moved from `vim` default output to its own output to reduce closure size. The canonical way to reference it across all platforms is `unixtools.xxd`.
 
+- `programs.fzf.keybindings` and `programs.fzf.fuzzyCompletion` got replaced by `programs.fzf.enabled` as shell-completion is included in the fzf-binary now there is no easy option to load completion and keybindings separately. Please consult fzf-documentation on how to configure/disable certain keybindings.
+
 - The `stalwart-mail` package has been updated to v0.5.3, which includes [breaking changes](https://github.com/stalwartlabs/mail-server/blob/v0.5.3/UPGRADING.md).
 
 - `services.zope2` has been removed as `zope2` is unmaintained and was relying on Python2.
@@ -376,12 +399,14 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   This means that configuration now has to be done using [environment variables](https://hexdocs.pm/livebook/readme.html#environment-variables) instead of command line arguments.
   This has the further implication that the `livebook` service configuration has changed:
 
-  - The `erlang_node_short_name`, `erlang_node_name`, `port` and `options` configuration parameters are gone, and have been replaced with an `environment` parameter.
+- The `erlang_node_short_name`, `erlang_node_name`, `port` and `options` configuration parameters are gone, and have been replaced with an `environment` parameter.
     Use the appropriate [environment variables](https://hexdocs.pm/livebook/readme.html#environment-variables) inside `environment` to configure the service instead.
 
 - The `crystal` package has been updated to 1.11.x, which has some breaking changes.
   Refer to crystal's changelog for more information. ([v1.10](https://github.com/crystal-lang/crystal/blob/master/CHANGELOG.md#1100-2023-10-09), [v1.11](https://github.com/crystal-lang/crystal/blob/master/CHANGELOG.md#1110-2024-01-08))
 
+- The `erlang-ls` package no longer ships the `els_dap` binary as of v0.51.0.
+
 ## Other Notable Changes {#sec-release-24.05-notable-changes}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
@@ -497,10 +522,13 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
 
 - `services.kavita` now uses the freeform option `services.kavita.settings` for the application settings file.
   The options `services.kavita.ipAdresses` and `services.kavita.port` now exist at `services.kavita.settings.IpAddresses`
-  and `services.kavita.settings.IpAddresses`.
+  and `services.kavita.settings.IpAddresses`. The file at `services.kavita.tokenKeyFile` now needs to contain a secret with
+  512+ bits instead of 128+ bits.
 
 - The `krb5` module has been rewritten and moved to `security.krb5`, moving all options but `security.krb5.enable` and `security.krb5.package` into `security.krb5.settings`.
 
+- `services.soju` now has a wrapper for the `sojuctl` command, pointed at the service config file. It also has the new option `adminSocket.enable`, which creates a unix admin socket at `/run/soju/admin`.
+
 - Gitea 1.21 upgrade has several breaking changes, including:
   - Custom themes and other assets that were previously stored in `custom/public/*` now belong in `custom/public/assets/*`
   - New instances of Gitea using MySQL now ignore the `[database].CHARSET` config option and always use the `utf8mb4` charset, existing instances should migrate via the `gitea doctor convert` CLI command.
diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix
index 832160111da4e..b67495609ff5a 100644
--- a/nixos/lib/systemd-lib.nix
+++ b/nixos/lib/systemd-lib.nix
@@ -525,4 +525,9 @@ in rec {
       )}
     '';
 
+  # The maximum number of characters allowed in a GPT partition label. This
+  # limit is specified by UEFI and enforced by systemd-repart.
+  # Corresponds to GPT_LABEL_MAX from systemd's gpt.h.
+  GPTMaxLabelLength = 36;
+
 }
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index c117aab7c4011..652cc600fad59 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -165,8 +165,6 @@ class StartCommand:
         )
         if not allow_reboot:
             qemu_opts += " -no-reboot"
-        # TODO: qemu script already catpures this env variable, legacy?
-        qemu_opts += " " + os.environ.get("QEMU_OPTS", "")
 
         return (
             f"{self._cmd}"
diff --git a/nixos/lib/testing/meta.nix b/nixos/lib/testing/meta.nix
index 8d053ce86d888..529fe714fcf6c 100644
--- a/nixos/lib/testing/meta.nix
+++ b/nixos/lib/testing/meta.nix
@@ -34,6 +34,13 @@ in
               Sets the [`meta.broken`](https://nixos.org/manual/nixpkgs/stable/#var-meta-broken) attribute on the [{option}`test`](#test-opt-test) derivation.
             '';
           };
+          platforms = lib.mkOption {
+            type = types.listOf types.raw;
+            default = lib.platforms.linux;
+            description = ''
+              Sets the [`meta.platforms`](https://nixos.org/manual/nixpkgs/stable/#var-meta-platforms) attribute on the [{option}`test`](#test-opt-test) derivation.
+            '';
+          };
         };
       };
       default = {};
diff --git a/nixos/modules/image/repart-image.nix b/nixos/modules/image/repart-image.nix
index 83e766268cf04..59d5fc26efe9b 100644
--- a/nixos/modules/image/repart-image.nix
+++ b/nixos/modules/image/repart-image.nix
@@ -41,6 +41,25 @@
 }:
 
 let
+  systemdArch = let
+    inherit (stdenvNoCC) hostPlatform;
+  in
+    if hostPlatform.isAarch32 then "arm"
+    else if hostPlatform.isAarch64 then "arm64"
+    else if hostPlatform.isx86_32 then "x86"
+    else if hostPlatform.isx86_64 then "x86-64"
+    else if hostPlatform.isMips32 then "mips-le"
+    else if hostPlatform.isMips64 then "mips64-le"
+    else if hostPlatform.isPower then "ppc"
+    else if hostPlatform.isPower64 then "ppc64"
+    else if hostPlatform.isRiscV32 then "riscv32"
+    else if hostPlatform.isRiscV64 then "riscv64"
+    else if hostPlatform.isS390 then "s390"
+    else if hostPlatform.isS390x then "s390x"
+    else if hostPlatform.isLoongArch64 then "loongarch64"
+    else if hostPlatform.isAlpha then "alpha"
+    else hostPlatform.parsed.cpu.name;
+
   amendRepartDefinitions = runCommand "amend-repart-definitions.py"
     {
       # TODO: ruff does not splice properly in nativeBuildInputs
@@ -99,6 +118,7 @@ in
   finalRepartDefinitions = "repart.d";
 
   systemdRepartFlags = [
+    "--architecture=${systemdArch}"
     "--dry-run=no"
     "--size=auto"
     "--seed=${seed}"
diff --git a/nixos/modules/image/repart.nix b/nixos/modules/image/repart.nix
index 1a43297f4b432..569d4a4b00215 100644
--- a/nixos/modules/image/repart.nix
+++ b/nixos/modules/image/repart.nix
@@ -6,6 +6,8 @@
 let
   cfg = config.image.repart;
 
+  inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
+
   partitionOptions = {
     options = {
       storePaths = lib.mkOption {
@@ -224,6 +226,42 @@ in
 
   config = {
 
+    assertions = lib.mapAttrsToList (fileName: partitionConfig:
+      let
+        inherit (partitionConfig) repartConfig;
+        labelLength = builtins.stringLength repartConfig.Label;
+      in
+      {
+        assertion = repartConfig ? Label -> GPTMaxLabelLength >= labelLength;
+        message = ''
+          The partition label '${repartConfig.Label}'
+          defined for '${fileName}' is ${toString labelLength} characters long,
+          but the maximum label length supported by UEFI is ${toString
+          GPTMaxLabelLength}.
+        '';
+      }
+    ) cfg.partitions;
+
+    warnings = lib.filter (v: v != null) (lib.mapAttrsToList (fileName: partitionConfig:
+      let
+        inherit (partitionConfig) repartConfig;
+        suggestedMaxLabelLength = GPTMaxLabelLength - 2;
+        labelLength = builtins.stringLength repartConfig.Label;
+      in
+        if (repartConfig ? Label && labelLength >= suggestedMaxLabelLength) then ''
+          The partition label '${repartConfig.Label}'
+          defined for '${fileName}' is ${toString labelLength} characters long.
+          The suggested maximum label length is ${toString
+          suggestedMaxLabelLength}.
+
+          If you use sytemd-sysupdate style A/B updates, this might
+          not leave enough space to increment the version number included in
+          the label in a future release. For example, if your label is
+          ${toString GPTMaxLabelLength} characters long (the maximum enforced by UEFI) and
+          you're at version 9, you cannot increment this to 10.
+        '' else null
+    ) cfg.partitions);
+
     image.repart =
       let
         version = config.image.repart.version;
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index e07295b4ba513..b361e9ee5e41d 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -842,6 +842,7 @@
   ./services/monitoring/munin.nix
   ./services/monitoring/nagios.nix
   ./services/monitoring/netdata.nix
+  ./services/monitoring/nezha-agent.nix
   ./services/monitoring/ocsinventory-agent.nix
   ./services/monitoring/opentelemetry-collector.nix
   ./services/monitoring/osquery.nix
@@ -1389,6 +1390,7 @@
   ./services/web-apps/rss-bridge.nix
   ./services/web-apps/selfoss.nix
   ./services/web-apps/shiori.nix
+  ./services/web-apps/silverbullet.nix
   ./services/web-apps/slskd.nix
   ./services/web-apps/snipe-it.nix
   ./services/web-apps/sogo.nix
diff --git a/nixos/modules/programs/fzf.nix b/nixos/modules/programs/fzf.nix
index 7c4f338e29b30..24fca7b332918 100644
--- a/nixos/modules/programs/fzf.nix
+++ b/nixos/modules/programs/fzf.nix
@@ -1,32 +1,46 @@
 { pkgs, config, lib, ... }:
+
 with lib;
+
 let
   cfg = config.programs.fzf;
+
 in
 {
+  imports = [
+    (lib.mkRemovedOptionModule [ "programs" "fzf" "keybindings" ] ''
+      Use "programs.fzf.enabled" instead, due to fzf upstream-change it's not possible to load shell-completion and keybindings separately.
+      If you want to change/disable certain keybindings please check the fzf-documentation.
+    '')
+    (lib.mkRemovedOptionModule [ "programs" "fzf" "fuzzyCompletion" ] ''
+      Use "programs.fzf.enabled" instead, due to fzf upstream-change it's not possible to load shell-completion and keybindings separately.
+      If you want to change/disable certain keybindings please check the fzf-documentation.
+    '')
+  ];
+
   options = {
-    programs.fzf = {
-      fuzzyCompletion = mkEnableOption (mdDoc "fuzzy completion with fzf");
-      keybindings = mkEnableOption (mdDoc "fzf keybindings");
-    };
+    programs.fzf.enable = mkEnableOption (mdDoc "fuzzy completion with fzf and keybindings");
   };
-  config = {
-    environment.systemPackages = optional (cfg.keybindings || cfg.fuzzyCompletion) pkgs.fzf;
 
-    programs.bash.interactiveShellInit = optionalString cfg.fuzzyCompletion ''
-      source ${pkgs.fzf}/share/fzf/completion.bash
-    '' + optionalString cfg.keybindings ''
-      source ${pkgs.fzf}/share/fzf/key-bindings.bash
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.fzf ];
+
+    programs.bash.interactiveShellInit = ''
+      eval "$(${getExe pkgs.fzf} --bash)"
+    '';
+
+    programs.fish.interactiveShellInit = ''
+      ${getExe pkgs.fzf} --fish | source
     '';
 
-    programs.zsh.interactiveShellInit = optionalString (!config.programs.zsh.ohMyZsh.enable)
-      (optionalString cfg.fuzzyCompletion ''
-        source ${pkgs.fzf}/share/fzf/completion.zsh
-      '' + optionalString cfg.keybindings ''
-        source ${pkgs.fzf}/share/fzf/key-bindings.zsh
-      '');
+    programs.zsh = {
+      interactiveShellInit = optionalString (!config.programs.zsh.ohMyZsh.enable) ''
+        eval "$(${getExe pkgs.fzf} --zsh)"
+      '';
 
-    programs.zsh.ohMyZsh.plugins = lib.mkIf (cfg.keybindings || cfg.fuzzyCompletion) [ "fzf" ];
+      ohMyZsh.plugins = mkIf (config.programs.zsh.ohMyZsh.enable) [ "fzf" ];
+    };
   };
+
   meta.maintainers = with maintainers; [ laalsaas ];
 }
diff --git a/nixos/modules/programs/oddjobd.nix b/nixos/modules/programs/oddjobd.nix
index 08bb8b2684731..9b19c160b2c02 100644
--- a/nixos/modules/programs/oddjobd.nix
+++ b/nixos/modules/programs/oddjobd.nix
@@ -4,26 +4,28 @@ let
   cfg = config.programs.oddjobd;
 in
 {
-  options.programs.oddjobd = {
-    enable = lib.mkEnableOption "oddjob";
-    package = lib.mkPackageOption pkgs "oddjob" {};
+  options = {
+    programs.oddjobd = {
+      enable = lib.mkEnableOption "oddjob";
+      package = lib.mkPackageOption pkgs "oddjob" {};
+    };
   };
 
   config = lib.mkIf cfg.enable {
-    systemd.packages = [ cfg.package ];
-
     systemd.services.oddjobd = {
-      wantedBy = [ "multi-user.target"];
-      after = [ "network.target"];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "dbus.service" ];
       description = "DBUS Odd-job Daemon";
       enable = true;
       documentation = [ "man:oddjobd(8)" "man:oddjobd.conf(5)" ];
       serviceConfig = {
-        Type = "dbus";
-        BusName = "org.freedesktop.oddjob";
-        ExecStart = "${lib.getBin cfg.package}/bin/oddjobd";
+        Type = "simple";
+        PIDFile = "/run/oddjobd.pid";
+        ExecStart = "${lib.getBin cfg.package}/bin/oddjobd -n -p /run/oddjobd.pid -t 300";
       };
     };
+
+    services.dbus.packages = [ cfg.package ];
   };
 
   meta.maintainers = with lib.maintainers; [ SohamG ];
diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix
index bab9bf8107b6e..d496af49cd9ce 100644
--- a/nixos/modules/programs/steam.nix
+++ b/nixos/modules/programs/steam.nix
@@ -55,6 +55,8 @@ in {
             then [ package ] ++ extraPackages
             else [ package32 ] ++ extraPackages32;
         in prevLibs ++ additionalLibs;
+        # ensure font packages are picked up by Steam
+        extraPkgs = (prev.extraPkgs or []) ++ config.fonts.packages;
       } // optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice)
       {
         buildFHSEnv = pkgs.buildFHSEnv.override {
diff --git a/nixos/modules/programs/wayland/hyprland.nix b/nixos/modules/programs/wayland/hyprland.nix
index 9061ce5da83a8..bb2641762cad9 100644
--- a/nixos/modules/programs/wayland/hyprland.nix
+++ b/nixos/modules/programs/wayland/hyprland.nix
@@ -13,7 +13,7 @@ in
 {
   options.programs.hyprland = {
     enable = mkEnableOption null // {
-      description = mdDoc ''
+      description = ''
         Hyprland, the dynamic tiling Wayland compositor that doesn't sacrifice on its looks.
 
         You can manually launch Hyprland by executing {command}`Hyprland` on a TTY.
@@ -33,14 +33,24 @@ in
       };
       defaultText = literalExpression
         "`programs.hyprland.package` with applied configuration";
-      description = mdDoc ''
+      description = ''
         The Hyprland package after applying configuration.
       '';
     };
 
     portalPackage = mkPackageOption pkgs "xdg-desktop-portal-hyprland" { };
 
-    xwayland.enable = mkEnableOption (mdDoc "XWayland") // { default = true; };
+    xwayland.enable = mkEnableOption ("XWayland") // { default = true; };
+
+    systemd.setPath.enable = mkEnableOption null // {
+      default = true;
+      example = false;
+      description = ''
+        Set environment path of systemd to include the current system's bin directory.
+        This is needed in Hyprland setups, where opening links in applications do not work.
+        Enabled by default.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -63,6 +73,12 @@ in
       extraPortals = [ finalPortalPackage ];
       configPackages = mkDefault [ cfg.finalPackage ];
     };
+
+    systemd = mkIf cfg.systemd.setPath.enable {
+      user.extraConfig = ''
+        DefaultEnvironment="PATH=$PATH:/run/current-system/sw/bin:/etc/profiles/per-user/$USER/bin:/run/wrappers/bin"
+      '';
+    };
   };
 
   imports = with lib; [
diff --git a/nixos/modules/programs/xss-lock.nix b/nixos/modules/programs/xss-lock.nix
index 87b3957ab834f..2ff45de93b6da 100644
--- a/nixos/modules/programs/xss-lock.nix
+++ b/nixos/modules/programs/xss-lock.nix
@@ -40,6 +40,7 @@ in
             "--"
             cfg.lockerCommand
         ]);
+      serviceConfig.Restart = "always";
     };
   };
 }
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 6f4455d3be605..ad6194f8262ae 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -147,6 +147,9 @@ let
     let
       settings = { inherit (cfg) user group; };
     in lib.nameValuePair "borgbackup-job-${name}" ({
+      # Create parent dirs separately, to ensure correct ownership.
+      "${config.users.users."${cfg.user}".home}/.config".d = settings;
+      "${config.users.users."${cfg.user}".home}/.cache".d = settings;
       "${config.users.users."${cfg.user}".home}/.config/borg".d = settings;
       "${config.users.users."${cfg.user}".home}/.cache/borg".d = settings;
     } // optionalAttrs (isLocalPath cfg.repo && !cfg.removableDevice) {
diff --git a/nixos/modules/services/desktop-managers/plasma6.nix b/nixos/modules/services/desktop-managers/plasma6.nix
index 5f1f2cec79e8d..796870aab1253 100644
--- a/nixos/modules/services/desktop-managers/plasma6.nix
+++ b/nixos/modules/services/desktop-managers/plasma6.nix
@@ -170,7 +170,7 @@ in {
         breeze.qt5
         plasma-integration.qt5
         pkgs.plasma5Packages.kwayland-integration
-        pkgs.plasma5Packages.kio
+        (pkgs.plasma5Packages.kio.override { withKcms = false; })
         kio-extras-kf5
       ]
       # Optional hardware support features
diff --git a/nixos/modules/services/matrix/mautrix-meta.nix b/nixos/modules/services/matrix/mautrix-meta.nix
index b8a5cdc72065b..f0905c3af1297 100644
--- a/nixos/modules/services/matrix/mautrix-meta.nix
+++ b/nixos/modules/services/matrix/mautrix-meta.nix
@@ -302,7 +302,7 @@ in {
   };
 
   config = lib.mkMerge [
-    (lib.mkIf (enabledInstances != []) {
+    (lib.mkIf (enabledInstances != {}) {
       assertions = lib.mkMerge (lib.attrValues (lib.mapAttrs (name: cfg: [
         {
           assertion = cfg.settings.homeserver.domain != "" && cfg.settings.homeserver.address != "";
diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix
index 7a5661510e251..30c2b26d8322e 100644
--- a/nixos/modules/services/misc/ollama.nix
+++ b/nixos/modules/services/misc/ollama.nix
@@ -15,6 +15,22 @@ in
     services.ollama = {
       enable = lib.mkEnableOption "ollama server for local large language models";
       package = lib.mkPackageOption pkgs "ollama" { };
+      home = lib.mkOption {
+        type = types.str;
+        default = "%S/ollama";
+        example = "/home/foo";
+        description = ''
+          The home directory that the ollama service is started in.
+        '';
+      };
+      models = lib.mkOption {
+        type = types.str;
+        default = "%S/ollama/models";
+        example = "/path/to/ollama/models";
+        description = ''
+          The directory that the ollama service will read models from and download new models to.
+        '';
+      };
       listenAddress = lib.mkOption {
         type = types.str;
         default = "127.0.0.1:11434";
@@ -58,8 +74,8 @@ in
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       environment = cfg.environmentVariables // {
-        HOME = "%S/ollama";
-        OLLAMA_MODELS = "%S/ollama/models";
+        HOME = cfg.home;
+        OLLAMA_MODELS = cfg.models;
         OLLAMA_HOST = cfg.listenAddress;
       };
       serviceConfig = {
diff --git a/nixos/modules/services/monitoring/nezha-agent.nix b/nixos/modules/services/monitoring/nezha-agent.nix
new file mode 100644
index 0000000000000..ef6878798f377
--- /dev/null
+++ b/nixos/modules/services/monitoring/nezha-agent.nix
@@ -0,0 +1,103 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.nezha-agent;
+in
+{
+  meta = {
+    maintainers = with lib.maintainers; [ moraxyc ];
+  };
+  options = {
+    services.nezha-agent = {
+      enable = lib.mkEnableOption (lib.mdDoc "Agent of Nezha Monitoring");
+
+      package = lib.mkPackageOption pkgs "nezha-agent" { };
+      debug = lib.mkEnableOption (lib.mdDoc "verbose log");
+      tls = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Enable SSL/TLS encryption.
+        '';
+      };
+      disableCommandExecute = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Disable executing the command from dashboard.
+        '';
+      };
+      skipConnection = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Do not monitor the number of connections.
+        '';
+      };
+      skipProcess = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Do not monitor the number of processes.
+        '';
+      };
+      reportDelay = lib.mkOption {
+        type = lib.types.enum [ 1 2 3 4 ];
+        default = 1;
+        description = lib.mdDoc ''
+          The interval between system status reportings.
+          The value must be an integer from 1 to 4
+        '';
+      };
+      passwordFile = lib.mkOption {
+        type = with lib.types; nullOr str;
+        default = null;
+        description = lib.mdDoc ''
+          Path to the file contained the password from dashboard.
+        '';
+      };
+      server = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc ''
+          Address to the dashboard
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [ cfg.package ];
+
+    systemd.services.nezha-agent = {
+      serviceConfig = {
+        ProtectSystem = "full";
+        PrivateDevices = "yes";
+        PrivateTmp = "yes";
+        NoNewPrivileges = true;
+      };
+      path = [ cfg.package ];
+      startLimitIntervalSec = 10;
+      startLimitBurst = 3;
+      script = lib.concatStringsSep " " (
+        [
+          "${cfg.package}/bin/agent"
+          "--disable-auto-update"
+          "--disable-force-update"
+          "--password $(cat ${cfg.passwordFile})"
+        ]
+        ++ lib.optional cfg.debug "--debug"
+        ++ lib.optional cfg.disableCommandExecute "--disable-command-execute"
+        ++ lib.optional (cfg.reportDelay != null) "--report-delay ${toString cfg.reportDelay}"
+        ++ lib.optional (cfg.server != null) "--server ${cfg.server}"
+        ++ lib.optional cfg.skipConnection "--skip-conn"
+        ++ lib.optional cfg.skipProcess "--skip-procs"
+        ++ lib.optional cfg.tls "--tls"
+      );
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix
index 02502816ef5d7..f9ec271c6ef55 100644
--- a/nixos/modules/services/monitoring/thanos.nix
+++ b/nixos/modules/services/monitoring/thanos.nix
@@ -353,6 +353,10 @@ let
         See <https://tools.ietf.org/html/rfc4366#section-3.1>
       '';
 
+      grpc-compression = mkParam types.str ''
+        Compression algorithm to use for gRPC requests to other clients.
+      '';
+
       web.route-prefix = mkParam types.str ''
         Prefix for API and UI endpoints.
 
@@ -642,6 +646,10 @@ let
 
     receive = params.common cfg.receive // params.objstore cfg.receive // {
 
+      receive.grpc-compression = mkParam types.str ''
+        Compression algorithm to use for gRPC requests to other receivers.
+      '';
+
       remote-write.address = mkParamDef types.str "0.0.0.0:19291" ''
         Address to listen on for remote write requests.
       '';
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index da8633d5066f7..0a4fa4c524a6c 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -121,7 +121,7 @@ in
       package = mkPackageOption pkgs "bind" { };
 
       cacheNetworks = mkOption {
-        default = [ "127.0.0.0/24" ];
+        default = [ "127.0.0.0/24" "::1/128" ];
         type = types.listOf types.str;
         description = lib.mdDoc ''
           What networks are allowed to use us as a resolver.  Note
diff --git a/nixos/modules/services/networking/mihomo.nix b/nixos/modules/services/networking/mihomo.nix
index ae700603b5290..312530caeaade 100644
--- a/nixos/modules/services/networking/mihomo.nix
+++ b/nixos/modules/services/networking/mihomo.nix
@@ -12,7 +12,7 @@ let
 in
 {
   options.services.mihomo = {
-    enable = lib.mkEnableOption "Mihomo, A rule-based proxy in Go.";
+    enable = lib.mkEnableOption "Mihomo, A rule-based proxy in Go";
 
     package = lib.mkPackageOption pkgs "mihomo" { };
 
@@ -28,14 +28,14 @@ in
       description = ''
         Local web interface to use.
 
-        You can also use the following website, just in case:
+        You can also use the following website:
         - metacubexd:
           - https://d.metacubex.one
           - https://metacubex.github.io/metacubexd
           - https://metacubexd.pages.dev
         - yacd:
           - https://yacd.haishan.me
-        - clash-dashboard (buggy):
+        - clash-dashboard:
           - https://clash.razord.top
       '';
     };
@@ -49,7 +49,7 @@ in
     tunMode = lib.mkEnableOption ''
       necessary permission for Mihomo's systemd service for TUN mode to function properly.
 
-      Keep in mind, that you still need to enable TUN mode manually in Mihomo's configuration.
+      Keep in mind, that you still need to enable TUN mode manually in Mihomo's configuration
     '';
   };
 
diff --git a/nixos/modules/services/networking/soju.nix b/nixos/modules/services/networking/soju.nix
index d69ec08ca13a0..810957be65f57 100644
--- a/nixos/modules/services/networking/soju.nix
+++ b/nixos/modules/services/networking/soju.nix
@@ -5,7 +5,10 @@ with lib;
 let
   cfg = config.services.soju;
   stateDir = "/var/lib/soju";
-  listenCfg = concatMapStringsSep "\n" (l: "listen ${l}") cfg.listen;
+  runtimeDir = "/run/soju";
+  listen = cfg.listen
+    ++ optional cfg.adminSocket.enable "unix+admin://${runtimeDir}/admin";
+  listenCfg = concatMapStringsSep "\n" (l: "listen ${l}") listen;
   tlsCfg = optionalString (cfg.tlsCertificate != null)
     "tls ${cfg.tlsCertificate} ${cfg.tlsCertificateKey}";
   logCfg = optionalString cfg.enableMessageLogging
@@ -22,6 +25,10 @@ let
 
     ${cfg.extraConfig}
   '';
+
+  sojuctl = pkgs.writeShellScriptBin "sojuctl" ''
+    exec ${cfg.package}/bin/sojuctl --config ${configFile} "$@"
+  '';
 in
 {
   ###### interface
@@ -29,6 +36,8 @@ in
   options.services.soju = {
     enable = mkEnableOption (lib.mdDoc "soju");
 
+    package = mkPackageOption pkgs "soju" { };
+
     listen = mkOption {
       type = types.listOf types.str;
       default = [ ":6697" ];
@@ -66,6 +75,14 @@ in
       description = lib.mdDoc "Whether to enable message logging.";
     };
 
+    adminSocket.enable = mkOption {
+      type = types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Listen for admin connections from sojuctl at /run/soju/admin.
+      '';
+    };
+
     httpOrigins = mkOption {
       type = types.listOf types.str;
       default = [];
@@ -107,6 +124,8 @@ in
       }
     ];
 
+    environment.systemPackages = [ sojuctl ];
+
     systemd.services.soju = {
       description = "soju IRC bouncer";
       wantedBy = [ "multi-user.target" ];
@@ -115,8 +134,9 @@ in
       serviceConfig = {
         DynamicUser = true;
         Restart = "always";
-        ExecStart = "${pkgs.soju}/bin/soju -config ${configFile}";
+        ExecStart = "${cfg.package}/bin/soju -config ${configFile}";
         StateDirectory = "soju";
+        RuntimeDirectory = "soju";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/healthchecks.nix b/nixos/modules/services/web-apps/healthchecks.nix
index 1d439f162313b..04b40e6eb8b08 100644
--- a/nixos/modules/services/web-apps/healthchecks.nix
+++ b/nixos/modules/services/web-apps/healthchecks.nix
@@ -213,8 +213,7 @@ in
           preStart = ''
             ${pkg}/opt/healthchecks/manage.py collectstatic --no-input
             ${pkg}/opt/healthchecks/manage.py remove_stale_contenttypes --no-input
-            ${pkg}/opt/healthchecks/manage.py compress
-          '';
+          '' + lib.optionalString (cfg.settings.DEBUG != "True") "${pkg}/opt/healthchecks/manage.py compress";
 
           serviceConfig = commonConfig // {
             Restart = "always";
diff --git a/nixos/modules/services/web-apps/hledger-web.nix b/nixos/modules/services/web-apps/hledger-web.nix
index be8ecc645e59c..2e888d081a9f9 100644
--- a/nixos/modules/services/web-apps/hledger-web.nix
+++ b/nixos/modules/services/web-apps/hledger-web.nix
@@ -26,28 +26,17 @@ in {
       '';
     };
 
-    capabilities = {
-      view = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Enable the view capability.
-        '';
-      };
-      add = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable the add capability.
-        '';
-      };
-      manage = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable the manage capability.
-        '';
-      };
+    allow = mkOption {
+      type = types.enum [ "view" "add" "edit" "sandstorm" ];
+      default = "view";
+      description = lib.mdDoc ''
+        User's access level for changing data.
+
+        * view: view only permission.
+        * add: view and add permissions.
+        * edit: view, add, and edit permissions.
+        * sandstorm: permissions from the `X-Sandstorm-Permissions` request header.
+      '';
     };
 
     stateDir = mkOption {
@@ -89,6 +78,11 @@ in {
 
   };
 
+  imports = [
+    (mkRemovedOptionModule [ "services" "hledger-web" "capabilities" ]
+      "This option has been replaced by new option `services.hledger-web.allow`.")
+  ];
+
   config = mkIf cfg.enable {
 
     users.users.hledger = {
@@ -102,16 +96,11 @@ in {
     users.groups.hledger = {};
 
     systemd.services.hledger-web = let
-      capabilityString = with cfg.capabilities; concatStringsSep "," (
-        (optional view "view")
-        ++ (optional add "add")
-        ++ (optional manage "manage")
-      );
       serverArgs = with cfg; escapeShellArgs ([
         "--serve"
         "--host=${host}"
         "--port=${toString port}"
-        "--capabilities=${capabilityString}"
+        "--allow=${allow}"
         (optionalString (cfg.baseUrl != null) "--base-url=${cfg.baseUrl}")
         (optionalString (cfg.serveApi) "--serve-api")
       ] ++ (map (f: "--file=${stateDir}/${f}") cfg.journalFiles)
diff --git a/nixos/modules/services/web-apps/kavita.nix b/nixos/modules/services/web-apps/kavita.nix
index c90697bcfa8b2..81b8edc5e0066 100644
--- a/nixos/modules/services/web-apps/kavita.nix
+++ b/nixos/modules/services/web-apps/kavita.nix
@@ -34,8 +34,8 @@ in
     tokenKeyFile = lib.mkOption {
       type = lib.types.path;
       description = lib.mdDoc ''
-        A file containing the TokenKey, a secret with at 128+ bits.
-        It can be generated with `head -c 32 /dev/urandom | base64`.
+        A file containing the TokenKey, a secret with at 512+ bits.
+        It can be generated with `head -c 64 /dev/urandom | base64 --wrap=0`.
       '';
     };
 
diff --git a/nixos/modules/services/web-apps/outline.nix b/nixos/modules/services/web-apps/outline.nix
index 09dd6f83f39ac..891d78d4045b4 100644
--- a/nixos/modules/services/web-apps/outline.nix
+++ b/nixos/modules/services/web-apps/outline.nix
@@ -784,7 +784,7 @@ in
         # onboarding files:
         WorkingDirectory = "${cfg.package}/share/outline";
         # In case this directory is not in /var/lib/outline, it needs to be made writable explicitly
-        ReadWritePaths = [ cfg.storage.localRootDir ];
+        ReadWritePaths = lib.mkIf (cfg.storage.storageType == "local") [ cfg.storage.localRootDir ];
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/silverbullet.nix b/nixos/modules/services/web-apps/silverbullet.nix
new file mode 100644
index 0000000000000..a0c6ee34d262f
--- /dev/null
+++ b/nixos/modules/services/web-apps/silverbullet.nix
@@ -0,0 +1,123 @@
+{ config
+, pkgs
+, lib
+, ...
+}:
+let
+  cfg = config.services.silverbullet;
+  defaultUser = "silverbullet";
+  defaultGroup = defaultUser;
+  defaultSpaceDir = "/var/lib/silverbullet";
+in
+{
+  options = {
+    services.silverbullet = {
+      enable = lib.mkEnableOption (lib.mdDoc "Silverbullet, an open-source, self-hosted, offline-capable Personal Knowledge Management (PKM) web application.");
+
+      package = lib.mkPackageOptionMD pkgs "silverbullet" { };
+
+      openFirewall = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc "Open port in the firewall.";
+      };
+
+      listenPort = lib.mkOption {
+        type = lib.types.int;
+        default = 3000;
+        description = lib.mdDoc "Port to listen on.";
+      };
+
+      listenAddress = lib.mkOption {
+        type = lib.types.str;
+        default = "127.0.0.1";
+        description = lib.mdDoc "Address or hostname to listen on. Defaults to 127.0.0.1.";
+      };
+
+      spaceDir = lib.mkOption {
+        type = lib.types.path;
+        default = defaultSpaceDir;
+        example = "/home/yourUser/silverbullet";
+        description = lib.mdDoc ''
+          Folder to store Silverbullet's space/workspace.
+          By default it is located at `${defaultSpaceDir}`.
+        '';
+      };
+
+      user = lib.mkOption {
+        type = lib.types.str;
+        default = defaultUser;
+        example = "yourUser";
+        description = lib.mdDoc ''
+          The user to run Silverbullet as.
+          By default, a user named `${defaultUser}` will be created whose space
+          directory is [spaceDir](#opt-services.silverbullet.spaceDir).
+        '';
+      };
+
+      group = lib.mkOption {
+        type = lib.types.str;
+        default = defaultGroup;
+        example = "yourGroup";
+        description = lib.mdDoc ''
+          The group to run Silverbullet under.
+          By default, a group named `${defaultGroup}` will be created.
+        '';
+      };
+
+      envFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/etc/silverbullet.env";
+        description = lib.mdDoc ''
+          File containing extra environment variables. For example:
+
+          ```
+          SB_USER=user:password
+          SB_AUTH_TOKEN=abcdefg12345
+          ```
+        '';
+      };
+
+      extraArgs = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
+        example = [ "--db /path/to/silverbullet.db" ];
+        description = lib.mdDoc "Extra arguments passed to silverbullet.";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.silverbullet = {
+      description = "Silverbullet service";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = lib.mkIf (!lib.hasPrefix "/var/lib/" cfg.spaceDir) "mkdir -p '${cfg.spaceDir}'";
+      serviceConfig = {
+        Type = "simple";
+        User = "${cfg.user}";
+        Group = "${cfg.group}";
+        EnvironmentFile = lib.mkIf (cfg.envFile != null) "${cfg.envFile}";
+        StateDirectory = lib.mkIf (lib.hasPrefix "/var/lib/" cfg.spaceDir) (lib.last (lib.splitString "/" cfg.spaceDir));
+        ExecStart = "${lib.getExe cfg.package} --port ${toString cfg.listenPort} --hostname '${cfg.listenAddress}' '${cfg.spaceDir}' " + lib.concatStringsSep " " cfg.extraArgs;
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = lib.mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listenPort ];
+    };
+
+    users.users.${defaultUser} = lib.mkIf (cfg.user == defaultUser) {
+      isSystemUser = true;
+      group = cfg.group;
+      description = "Silverbullet daemon user";
+    };
+
+    users.groups.${defaultGroup} = lib.mkIf (cfg.group == defaultGroup) { };
+  };
+
+  meta.maintainers = with lib.maintainers; [ aorith ];
+}
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 2cfdc69b86e06..b9ca6bd4ba8d3 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -190,22 +190,6 @@ in
         "org.gnome.SettingsDaemon.XSettings.service"
       ];
 
-      # https://github.com/elementary/gala/issues/1826#issuecomment-1890461298
-      systemd.user.services."io.elementary.gala.daemon@" = {
-        unitConfig = {
-          Description = "Gala Daemon";
-          BindsTo = "io.elementary.gala@.service";
-          After = "io.elementary.gala@.service";
-        };
-
-        serviceConfig = {
-          Type = "dbus";
-          BusName = "org.pantheon.gala.daemon";
-          ExecStart = "${pkgs.pantheon.gala}/bin/gala-daemon";
-          Slice = "session.slice";
-        };
-      };
-
       # Global environment
       environment.systemPackages = (with pkgs.pantheon; [
         elementary-session-settings
diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix
index 6cc387cb6f43f..7771f5fe06789 100644
--- a/nixos/modules/system/boot/systemd/repart.nix
+++ b/nixos/modules/system/boot/systemd/repart.nix
@@ -13,14 +13,14 @@ let
 
   partitionAssertions = lib.mapAttrsToList (fileName: definition:
     let
-      maxLabelLength = 36; # GPT_LABEL_MAX defined in systemd's gpt.h
+      inherit (utils.systemdUtils.lib) GPTMaxLabelLength;
       labelLength = builtins.stringLength definition.Label;
     in
     {
-      assertion = definition ? Label -> maxLabelLength >= labelLength;
+      assertion = definition ? Label -> GPTMaxLabelLength >= labelLength;
       message = ''
         The partition label '${definition.Label}' defined for '${fileName}' is ${toString labelLength}
-        characters long, but the maximum label length supported by systemd is ${toString maxLabelLength}.
+        characters long, but the maximum label length supported by systemd is ${toString GPTMaxLabelLength}.
       '';
     }
   ) cfg.partitions;
diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix
index ba33edd702f70..d7e83464391c4 100644
--- a/nixos/modules/tasks/filesystems/bcachefs.nix
+++ b/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -57,7 +57,9 @@ let
   # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
   firstDevice = fs: lib.head (lib.splitString ":" fs.device);
 
-  openCommand = name: fs: if config.boot.initrd.clevis.enable && (lib.hasAttr (firstDevice fs) config.boot.initrd.clevis.devices) then ''
+  useClevis = fs: config.boot.initrd.clevis.enable && (lib.hasAttr (firstDevice fs) config.boot.initrd.clevis.devices);
+
+  openCommand = name: fs: if useClevis fs then ''
     if clevis decrypt < /etc/clevis/${firstDevice fs}.jwe | bcachefs unlock ${firstDevice fs}
     then
       printf "unlocked ${name} using clevis\n"
@@ -92,8 +94,19 @@ let
         # As is, RemainAfterExit doesn't accomplish anything.
         RemainAfterExit = true;
       };
-      script = ''
-        ${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${pkgs.bcachefs-tools}/bin/bcachefs unlock "${device}"
+      script = let
+        unlock = ''${pkgs.bcachefs-tools}/bin/bcachefs unlock "${device}"'';
+        unlockInteractively = ''${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${unlock}'';
+      in if useClevis fs then ''
+        if ${config.boot.initrd.clevis.package}/bin/clevis decrypt < "/etc/clevis/${device}.jwe" | ${unlock}
+        then
+          printf "unlocked ${name} using clevis\n"
+        else
+          printf "falling back to interactive unlocking...\n"
+          ${unlockInteractively}
+        fi
+      '' else ''
+        ${unlockInteractively}
       '';
     };
   };
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index d11424c11c810..a3e332acbda90 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -576,6 +576,8 @@ in
             copy_bin_and_libs ${cfgZfs.package}/sbin/zfs
             copy_bin_and_libs ${cfgZfs.package}/sbin/zdb
             copy_bin_and_libs ${cfgZfs.package}/sbin/zpool
+            copy_bin_and_libs ${cfgZfs.package}/lib/udev/vdev_id
+            copy_bin_and_libs ${cfgZfs.package}/lib/udev/zvol_id
           '';
         extraUtilsCommandsTest =
           mkIf (!config.boot.initrd.systemd.enable) ''
@@ -632,7 +634,12 @@ in
             zfs = "${cfgZfs.package}/sbin/zfs";
             awk = "${pkgs.gawk}/bin/awk";
           };
+          storePaths = [
+            "${cfgZfs.package}/lib/udev/vdev_id"
+            "${cfgZfs.package}/lib/udev/zvol_id"
+          ];
         };
+        services.udev.packages = [cfgZfs.package]; # to hook zvol naming, in stage 1
       };
 
       systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/zpool".source = pkgs.writeShellScript "zpool-sync-shutdown" ''
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index 50a54a006415d..b07433f5c18fd 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -170,7 +170,7 @@ in
       # thing, but for VM tests it should provide a bit more
       # determinism (e.g. if the VM runs at lower speed, then
       # timeouts in the VM should also be delayed).
-      "clock=acpi_pm"
+      "clocksource=acpi_pm"
     ];
 
     # `xwininfo' is used by the test driver to query open windows.
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 5db3a336f85d5..bde1ff9eeb98d 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -834,7 +834,10 @@ in
               script = startScript containerConfig;
               postStart = postStartScript containerConfig;
               serviceConfig = serviceDirectives containerConfig;
-              unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "${stateDirectory}/%i";
+              unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "${stateDirectory}/%i"
+                ++ builtins.map
+                  (d: if d.hostPath != null then d.hostPath else d.mountPoint)
+                  (builtins.attrValues cfg.bindMounts);
               environment.root = if containerConfig.ephemeral then "/run/nixos-containers/%i" else "${stateDirectory}/%i";
             } // (
             optionalAttrs containerConfig.autoStart
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index b5a8b08eee70d..a8e5db4f8a5ba 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -186,7 +186,7 @@ let
         NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}")
         # VM needs writable EFI vars
         if ! test -e "$NIX_EFI_VARS"; then
-        ${if cfg.useBootLoader then
+        ${if cfg.efi.keepVariables then
             # We still need the EFI var from the make-disk-image derivation
             # because our "switch-to-configuration" process might
             # write into it and we want to keep this data.
@@ -905,6 +905,13 @@ in
             Defaults to OVMF.
           '';
       };
+
+      keepVariables = mkOption {
+        type = types.bool;
+        default = cfg.useBootLoader;
+        defaultText = literalExpression "cfg.useBootLoader";
+        description = "Whether to keep EFI variable values from the generated system image";
+      };
     };
 
     virtualisation.tpm = {
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 487a7755bb17f..6f78d68730c91 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -219,6 +219,7 @@ in {
   containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {};
   containers-portforward = handleTest ./containers-portforward.nix {};
   containers-reloadable = handleTest ./containers-reloadable.nix {};
+  containers-require-bind-mounts = handleTest ./containers-require-bind-mounts.nix {};
   containers-restart_networking = handleTest ./containers-restart_networking.nix {};
   containers-tmpfs = handleTest ./containers-tmpfs.nix {};
   containers-unified-hierarchy = handleTest ./containers-unified-hierarchy.nix {};
@@ -651,6 +652,7 @@ in {
   nzbget = handleTest ./nzbget.nix {};
   nzbhydra2 = handleTest ./nzbhydra2.nix {};
   ocis = handleTest ./ocis.nix {};
+  oddjobd = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./oddjobd.nix {};
   oh-my-zsh = handleTest ./oh-my-zsh.nix {};
   ollama = handleTest ./ollama.nix {};
   ombi = handleTest ./ombi.nix {};
@@ -811,6 +813,7 @@ in {
   shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
   shiori = handleTest ./shiori.nix {};
   signal-desktop = handleTest ./signal-desktop.nix {};
+  silverbullet = handleTest ./silverbullet.nix {};
   simple = handleTest ./simple.nix {};
   sing-box = handleTest ./sing-box.nix {};
   slimserver = handleTest ./slimserver.nix {};
@@ -823,6 +826,7 @@ in {
   soapui = handleTest ./soapui.nix {};
   soft-serve = handleTest ./soft-serve.nix {};
   sogo = handleTest ./sogo.nix {};
+  soju = handleTest ./soju.nix {};
   solanum = handleTest ./solanum.nix {};
   sonarr = handleTest ./sonarr.nix {};
   sonic-server = handleTest ./sonic-server.nix {};
diff --git a/nixos/tests/containers-require-bind-mounts.nix b/nixos/tests/containers-require-bind-mounts.nix
new file mode 100644
index 0000000000000..5f986fd3e2803
--- /dev/null
+++ b/nixos/tests/containers-require-bind-mounts.nix
@@ -0,0 +1,35 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "containers-require-bind-mounts";
+  meta.maintainers = with lib.maintainers; [ kira-bruneau ];
+
+  nodes.machine = {
+    containers.require-bind-mounts = {
+      bindMounts = { "/srv/data" = {}; };
+      config = {};
+    };
+
+    virtualisation.fileSystems = {
+      "/srv/data" = {
+        fsType = "tmpfs";
+        options = [ "noauto" ];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("default.target")
+
+    assert "require-bind-mounts" in machine.succeed("nixos-container list")
+    assert "down" in machine.succeed("nixos-container status require-bind-mounts")
+    assert "inactive" in machine.fail("systemctl is-active srv-data.mount")
+
+    with subtest("bind mount host paths must be mounted to run container"):
+      machine.succeed("nixos-container start require-bind-mounts")
+      assert "up" in machine.succeed("nixos-container status require-bind-mounts")
+      assert "active" in machine.succeed("systemctl status srv-data.mount")
+
+      machine.succeed("systemctl stop srv-data.mount")
+      assert "down" in machine.succeed("nixos-container status require-bind-mounts")
+      assert "inactive" in machine.fail("systemctl is-active srv-data.mount")
+    '';
+})
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index 7d91076600f97..c8a227eb2cf7b 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -178,6 +178,14 @@ in {
             "docker load --input='${examples.bashUncompressed}'",
             "docker rmi ${examples.bashUncompressed.imageName}",
         )
+        docker.succeed(
+            "docker load --input='${examples.bashLayeredUncompressed}'",
+            "docker rmi ${examples.bashLayeredUncompressed.imageName}",
+        )
+        docker.succeed(
+            "docker load --input='${examples.bashLayeredZstdCompressed}'",
+            "docker rmi ${examples.bashLayeredZstdCompressed.imageName}",
+        )
 
     with subtest(
         "Check if the nix store is correctly initialized by listing "
diff --git a/nixos/tests/hledger-web.nix b/nixos/tests/hledger-web.nix
index f8919f7d4bd06..09941ca5c517c 100644
--- a/nixos/tests/hledger-web.nix
+++ b/nixos/tests/hledger-web.nix
@@ -19,7 +19,7 @@ rec {
         host = "127.0.0.1";
         port = 5000;
         enable = true;
-        capabilities.manage = true;
+        allow = "edit";
       };
       networking.firewall.allowedTCPPorts = [ config.services.hledger-web.port ];
       systemd.services.hledger-web.preStart = ''
diff --git a/nixos/tests/installer-systemd-stage-1.nix b/nixos/tests/installer-systemd-stage-1.nix
index d10256d91d7fa..1dd55dada042a 100644
--- a/nixos/tests/installer-systemd-stage-1.nix
+++ b/nixos/tests/installer-systemd-stage-1.nix
@@ -38,6 +38,8 @@
     clevisZfs
     clevisZfsFallback
     gptAutoRoot
+    clevisBcachefs
+    clevisBcachefsFallback
     ;
 
 }
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 1de886d6a0d19..7e835041eb39f 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -51,7 +51,7 @@ let
           boot.loader.systemd-boot.enable = true;
         ''}
 
-        boot.initrd.secrets."/etc/secret" = ./secret;
+        boot.initrd.secrets."/etc/secret" = "/etc/nixos/secret";
 
         ${optionalString clevisTest ''
           boot.kernelParams = [ "console=tty0" "ip=192.168.1.1:::255.255.255.0::eth1:none" ];
@@ -80,39 +80,24 @@ let
   # a test script fragment `createPartitions', which must create
   # partitions and filesystems.
   testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi, grubIdentifier
-                  , postInstallCommands, preBootCommands, postBootCommands, extraConfig
+                  , postInstallCommands, postBootCommands, extraConfig
                   , testSpecialisationConfig, testFlakeSwitch, clevisTest, clevisFallbackTest
                   , disableFileSystems
                   }:
     let
-      qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
-      isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
-      qemu = qemu-common.qemuBinary pkgs.qemu_test;
-    in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
-      machine.succeed("true")
-    '' else ''
+      startTarget = ''
+        ${optionalString clevisTest "tpm.start()"}
+        target.start()
+        ${postBootCommands}
+        target.wait_for_unit("multi-user.target")
+      '';
+    in ''
+      ${optionalString clevisTest ''
       import os
       import subprocess
 
       tpm_folder = os.environ['NIX_BUILD_TOP']
 
-      startcommand = "${qemu} -m 2048"
-
-      ${optionalString clevisTest ''
-        startcommand += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"
-        startcommand += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""
-      ''}
-      ${optionalString isEfi ''
-        startcommand +=" -drive if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware} -drive if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}"
-      ''}
-
-      image_dir = machine.state_dir
-      disk_image = os.path.join(image_dir, "machine.qcow2")
-      startcommand += f" -drive file={disk_image},if=virtio,werror=report"
-
-      def create_machine_named(name):
-          return create_machine(startcommand, name=name)
-
       class Tpm:
             def __init__(self):
                 self.start()
@@ -143,30 +128,31 @@ let
       os.mkdir(f"{tpm_folder}/swtpm")
       tpm = Tpm()
       tpm.check()
+      ''}
 
-      start_all()
+      installer.start()
       ${optionalString clevisTest ''
+      tang.start()
       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")
+      installer.systemctl("start network-online.target")
+      installer.wait_for_unit("network-online.target")
       ''}
-      machine.wait_for_unit("multi-user.target")
-
+      installer.wait_for_unit("multi-user.target")
 
       with subtest("Assert readiness of login prompt"):
-          machine.succeed("echo hello")
+          installer.succeed("echo hello")
 
       with subtest("Wait for hard disks to appear in /dev"):
-          machine.succeed("udevadm settle")
+          installer.succeed("udevadm settle")
 
       ${createPartitions}
 
       with subtest("Create the NixOS configuration"):
-          machine.succeed("nixos-generate-config ${optionalString disableFileSystems "--no-filesystems"} --root /mnt")
-          machine.succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2")
-          machine.copy_from_host(
+          installer.succeed("nixos-generate-config ${optionalString disableFileSystems "--no-filesystems"} --root /mnt")
+          installer.succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2")
+          installer.copy_from_host(
               "${ makeConfig {
                     inherit bootLoader grubDevice grubIdentifier
                             grubUseEfi extraConfig clevisTest;
@@ -174,13 +160,13 @@ let
               }",
               "/mnt/etc/nixos/configuration.nix",
           )
-          machine.copy_from_host("${pkgs.writeText "secret" "secret"}", "/mnt/etc/nixos/secret")
+          installer.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')''}
+             installer.systemctl("start network-online.target")
+             installer.wait_for_unit("network-online.target")
+             installer.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"):
@@ -188,13 +174,13 @@ let
       ''}
 
       with subtest("Perform the installation"):
-          machine.succeed("nixos-install < /dev/null >&2")
+          installer.succeed("nixos-install < /dev/null >&2")
 
       with subtest("Do it again to make sure it's idempotent"):
-          machine.succeed("nixos-install < /dev/null >&2")
+          installer.succeed("nixos-install < /dev/null >&2")
 
       with subtest("Check that we can build things in nixos-enter"):
-          machine.succeed(
+          installer.succeed(
               """
               nixos-enter -- nix-build --option substitute false -E 'derivation {
                   name = "t";
@@ -209,48 +195,48 @@ let
       ${postInstallCommands}
 
       with subtest("Shutdown system after installation"):
-          machine.succeed("umount -R /mnt")
-          machine.succeed("sync")
-          machine.shutdown()
+          installer.succeed("umount -R /mnt")
+          installer.succeed("sync")
+          installer.shutdown()
 
-      # Now see if we can boot the installation.
-      machine = create_machine_named("boot-after-install")
+      # We're actually the same machine, just booting differently this time.
+      target.state_dir = installer.state_dir
 
-      # For example to enter LUKS passphrase.
-      ${preBootCommands}
+      # Now see if we can boot the installation.
+      ${startTarget}
 
       with subtest("Assert that /boot get mounted"):
-          machine.wait_for_unit("local-fs.target")
+          target.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")''
+              then ''target.succeed("test -e /boot/grub")''
+              else ''target.succeed("test -e /boot/loader/loader.conf")''
           }
 
       with subtest("Check whether /root has correct permissions"):
-          assert "700" in machine.succeed("stat -c '%a' /root")
+          assert "700" in target.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")
+          target.wait_for_unit("swap.target")
+          target.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")
+          target.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")
+          target.succeed("nix-env -iA nixos.procps >&2")
+          assert ".nix-profile" in target.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")
+          target.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(
+          target.copy_from_host_via_shell(
               "${ makeConfig {
                     inherit bootLoader grubDevice grubIdentifier
                             grubUseEfi extraConfig clevisTest;
@@ -261,25 +247,23 @@ let
           )
 
       with subtest("Check whether nixos-rebuild works"):
-          machine.succeed("nixos-rebuild switch >&2")
+          target.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")
+      #     kernel_modules = target.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()
+      target.shutdown()
 
       # Check whether a writable store build works
-      machine = create_machine_named("rebuild-switch")
-      ${preBootCommands}
-      machine.wait_for_unit("multi-user.target")
+      ${startTarget}
 
       # 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(
+      target.copy_from_host_via_shell(
           "${ makeConfig {
                 inherit bootLoader grubDevice grubIdentifier
                 grubUseEfi extraConfig clevisTest;
@@ -288,73 +272,62 @@ let
           }",
           "/etc/nixos/configuration.nix",
       )
-      machine.succeed("nixos-rebuild boot >&2")
-      machine.shutdown()
+      target.succeed("nixos-rebuild boot >&2")
+      target.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")
+      # And just to be sure, check that the target still boots after "nixos-rebuild switch".
+      ${startTarget}
+      target.wait_for_unit("network.target")
 
       # Sanity check, is it the configuration.nix we generated?
-      hostname = machine.succeed("hostname").strip()
+      hostname = target.succeed("hostname").strip()
       assert hostname == "thatworked"
 
-      ${postBootCommands}
-      machine.shutdown()
+      target.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")
+      # Reboot target
+      ${startTarget}
 
       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")
+          target.succeed("cat /run/booted-system/configuration-name >&2")
+          assert "Home" in target.succeed("cat /run/booted-system/configuration-name")
 
       with subtest("We should **not** find a file named /etc/gitconfig"):
-          machine.fail("test -e /etc/gitconfig")
+          target.fail("test -e /etc/gitconfig")
 
       with subtest("Set grub to boot the second configuration"):
-          machine.succeed("grub-reboot 1")
+          target.succeed("grub-reboot 1")
 
-      ${postBootCommands}
-      machine.shutdown()
+      target.shutdown()
 
-      # Reboot Machine
-      machine = create_machine_named("clone-alternate-config")
-      ${preBootCommands}
+      # Reboot target
+      ${startTarget}
 
-      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")
+          target.succeed("cat /run/booted-system/configuration-name >&2")
+          assert "Work" in target.succeed("cat /run/booted-system/configuration-name")
 
       with subtest("We should find a file named /etc/gitconfig"):
-          machine.succeed("test -e /etc/gitconfig")
+          target.succeed("test -e /etc/gitconfig")
 
-      ${postBootCommands}
-      machine.shutdown()
+      target.shutdown()
     ''
     + optionalString testFlakeSwitch ''
-      ${preBootCommands}
-      machine.start()
+      ${startTarget}
 
       with subtest("Configure system with flake"):
         # TODO: evaluate as user?
-        machine.succeed("""
+        target.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(
+        target.copy_from_host_via_shell(
           "${makeConfig {
                inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
                forceGrubReinstallCount = 1;
@@ -362,11 +335,11 @@ let
             }}",
           "/root/my-config/configuration.nix",
         )
-        machine.copy_from_host_via_shell(
+        target.copy_from_host_via_shell(
           "${./installer/flake.nix}",
           "/root/my-config/flake.nix",
         )
-        machine.succeed("""
+        target.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
@@ -378,36 +351,32 @@ let
         """)
 
       with subtest("Switch to flake based config"):
-        machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
-
-      ${postBootCommands}
-      machine.shutdown()
+        target.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
 
-      ${preBootCommands}
-      machine.start()
+      target.shutdown()
 
-      machine.wait_for_unit("multi-user.target")
+      ${startTarget}
 
       with subtest("nix-channel command is not available anymore"):
-        machine.succeed("! which nix-channel")
+        target.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("""
+        target.succeed("""
           [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]]
         """)
 
       with subtest("<nixpkgs> does not resolve"):
-        machine.succeed("""
+        target.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")
+        target.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
 
       with subtest("Evaluate flake config in fresh env without channel profiles"):
-        machine.succeed("""
+        target.succeed("""
           (
             exec 1>&2
             rm -v /root/.nix-channels
@@ -415,16 +384,15 @@ let
             rm -vrf /nix/var/nix/profiles/per-user/root/channels*
           )
         """)
-        machine.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
+        target.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
 
-      ${postBootCommands}
-      machine.shutdown()
+      target.shutdown()
     '';
 
 
   makeInstallerTest = name:
     { createPartitions
-    , postInstallCommands ? "", preBootCommands ? "", postBootCommands ? ""
+    , postInstallCommands ? "", postBootCommands ? ""
     , extraConfig ? ""
     , extraInstallerConfig ? {}
     , bootLoader ? "grub" # either "grub" or "systemd-boot"
@@ -436,18 +404,39 @@ let
     , clevisFallbackTest ? false
     , disableFileSystems ? false
     }:
-    makeTest {
+    let
+      isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
+    in makeTest {
       inherit enableOCR;
       name = "installer-" + name;
       meta = {
         # put global maintainers here, individuals go into makeInstallerTest fkt call
         maintainers = (meta.maintainers or []);
+        # non-EFI tests can only run on x86
+        platforms = if isEfi then platforms.linux else [ "x86_64-linux" "i686-linux" ];
       };
-      nodes = {
+      nodes = let
+        commonConfig = {
+          # builds stuff in the VM, needs more juice
+          virtualisation.diskSize = 8 * 1024;
+          virtualisation.cores = 8;
+          virtualisation.memorySize = 2048;
+
+          # both installer and target need to use the same drive
+          virtualisation.diskImage = "./target.qcow2";
 
-        # The configuration of the machine used to run "nixos-install".
-        machine = { pkgs, ... }: {
+          # and the same TPM options
+          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"
+          ];
+        };
+      in {
+        # The configuration of the system used to run "nixos-install".
+        installer = {
           imports = [
+            commonConfig
             ../modules/profiles/installation-device.nix
             ../modules/profiles/base.nix
             extraInstallerConfig
@@ -458,11 +447,6 @@ let
           # root filesystem.
           virtualisation.fileSystems."/".autoFormat = systemdStage1;
 
-          # builds stuff in the VM, needs more juice
-          virtualisation.diskSize = 8 * 1024;
-          virtualisation.cores = 8;
-          virtualisation.memorySize = 2048;
-
           boot.initrd.systemd.enable = systemdStage1;
 
           # Use a small /dev/vdb as the root disk for the
@@ -470,17 +454,6 @@ let
           # 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;
 
@@ -520,7 +493,13 @@ let
           in [
             (pkgs.grub2.override { inherit zfsSupport; })
             (pkgs.grub2_efi.override { inherit zfsSupport; })
-          ]) ++ optionals clevisTest [ pkgs.klibc ];
+          ])
+          ++ optionals (bootLoader == "systemd-boot") [
+            pkgs.zstd.bin
+            pkgs.mypy
+            pkgs.bootspec
+          ]
+          ++ optionals clevisTest [ pkgs.klibc ];
 
           nix.settings = {
             substituters = mkForce [];
@@ -529,6 +508,18 @@ let
           };
         };
 
+        target = {
+          imports = [ commonConfig ];
+          virtualisation.useBootLoader = true;
+          virtualisation.useEFIBoot = isEfi;
+          virtualisation.useDefaultFilesystems = false;
+          virtualisation.efi.keepVariables = false;
+
+          virtualisation.fileSystems."/" = {
+            device = "/dev/disk/by-label/this-is-not-real-and-will-never-be-used";
+            fsType = "ext4";
+          };
+        };
       } // optionalAttrs clevisTest {
         tang = {
           services.tang = {
@@ -541,7 +532,7 @@ let
       };
 
       testScript = testScriptFun {
-        inherit bootLoader createPartitions postInstallCommands preBootCommands postBootCommands
+        inherit bootLoader createPartitions postInstallCommands postBootCommands
                 grubDevice grubIdentifier grubUseEfi extraConfig
                 testSpecialisationConfig testFlakeSwitch clevisTest clevisFallbackTest
                 disableFileSystems;
@@ -550,7 +541,7 @@ let
 
     makeLuksRootTest = name: luksFormatOpts: makeInstallerTest name {
       createPartitions = ''
-        machine.succeed(
+        installer.succeed(
             "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
             + " mkpart primary ext2 1M 100MB"  # /boot
             + " mkpart primary linux-swap 100M 1024M"
@@ -572,10 +563,9 @@ let
         boot.kernelParams = lib.mkAfter [ "console=tty0" ];
       '';
       enableOCR = true;
-      preBootCommands = ''
-        machine.start()
-        machine.wait_for_text("[Pp]assphrase for")
-        machine.send_chars("supersecret\n")
+      postBootCommands = ''
+        target.wait_for_text("[Pp]assphrase for")
+        target.send_chars("supersecret\n")
       '';
     };
 
@@ -583,7 +573,7 @@ let
   # one big filesystem partition.
   simple-test-config = {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
           + " mkpart primary linux-swap 1M 1024M"
           + " mkpart primary ext2 1024M -1s",
@@ -602,7 +592,7 @@ let
 
   simple-uefi-grub-config = {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
           + " mkpart ESP fat32 1M 100MiB"  # /boot
           + " set 1 boot on"
@@ -656,7 +646,7 @@ let
       environment.systemPackages = with pkgs; [ keyutils clevis ];
     };
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
         + " mkpart primary ext2 1M 100MB"
         + " mkpart primary linux-swap 100M 1024M"
@@ -680,13 +670,9 @@ let
       # 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")
+    postBootCommands = optionalString fallback ''
+      target.wait_for_text("enter passphrase for")
+      target.send_chars("password\n")
     '';
   };
 
@@ -698,7 +684,7 @@ let
       environment.systemPackages = with pkgs; [ clevis ];
     };
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
         + " mkpart primary ext2 1M 100MB"
         + " mkpart primary linux-swap 100M 1024M"
@@ -719,17 +705,13 @@ let
     extraConfig = ''
       boot.initrd.clevis.devices."crypt-root".secretFile = "/etc/nixos/clevis-secret.jwe";
     '';
-    preBootCommands = ''
-      tpm = Tpm()
-      tpm.check()
-    '' + optionalString fallback ''
-      machine.start()
+    postBootCommands = optionalString fallback ''
       ${if systemdStage1 then ''
-      machine.wait_for_text("Please enter")
+      target.wait_for_text("Please enter")
       '' else ''
-      machine.wait_for_text("Passphrase for")
+      target.wait_for_text("Passphrase for")
       ''}
-      machine.send_chars("password\n")
+      target.send_chars("password\n")
     '';
   };
 
@@ -742,7 +724,7 @@ let
       environment.systemPackages = with pkgs; [ clevis ];
     };
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
         + " mkpart primary ext2 1M 100MB"
         + " mkpart primary linux-swap 100M 1024M"
@@ -770,17 +752,13 @@ let
       boot.zfs.devNodes = "/dev/disk/by-uuid/";
       networking.hostId = "00000000";
     '';
-    preBootCommands = ''
-      tpm = Tpm()
-      tpm.check()
-    '' + optionalString fallback ''
-      machine.start()
+    postBootCommands = optionalString fallback ''
       ${if systemdStage1 then ''
-      machine.wait_for_text("Enter key for rpool/root")
+      target.wait_for_text("Enter key for rpool/root")
       '' else ''
-      machine.wait_for_text("Key load error")
+      target.wait_for_text("Key load error")
       ''}
-      machine.send_chars("password\n")
+      target.send_chars("password\n")
     '';
   };
 
@@ -801,7 +779,7 @@ in {
   # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
   simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot" {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
           + " mkpart ESP fat32 1M 100MiB"  # /boot
           + " set 1 boot on"
@@ -828,7 +806,7 @@ in {
   # Same as the previous, but now with a separate /boot partition.
   separateBoot = makeInstallerTest "separateBoot" {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
           + " mkpart primary ext2 1M 100MB"  # /boot
           + " mkpart primary linux-swap 100MB 1024M"
@@ -848,7 +826,7 @@ in {
   # Same as the previous, but with fat32 /boot.
   separateBootFat = makeInstallerTest "separateBootFat" {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
           + " mkpart primary ext2 1M 100MB"  # /boot
           + " mkpart primary linux-swap 100MB 1024M"
@@ -880,7 +858,7 @@ in {
     '';
 
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
           + " mkpart primary ext2 1M 256MB"   # /boot
           + " mkpart primary linux-swap 256MB 1280M"
@@ -932,8 +910,8 @@ in {
     # 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")
+      installer.succeed("umount /mnt/boot")
+      installer.succeed("zpool export bpool")
     '';
   };
 
@@ -954,7 +932,7 @@ in {
     '';
 
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
           + " mkpart primary 1M 100MB"  # /boot
           + " mkpart primary linux-swap 100M 1024M"
@@ -980,7 +958,7 @@ in {
   # that contains the logical swap and root partitions.
   lvm = makeInstallerTest "lvm" {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
           + " mkpart primary 1M 2048M"  # PV1
           + " set 1 lvm on"
@@ -1013,7 +991,7 @@ in {
   # keyfile is configured
   encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile" {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
           + " mkpart primary ext2 1M 100MB"  # /boot
           + " mkpart primary linux-swap 100M 1024M"
@@ -1052,7 +1030,7 @@ in {
   # LVM-on-LUKS and a keyfile in initrd.secrets to enter the passphrase once
   fullDiskEncryption = makeInstallerTest "fullDiskEncryption" {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
           + " mkpart ESP fat32 1M 100MiB"  # /boot/efi
           + " set 1 boot on"
@@ -1083,23 +1061,22 @@ in {
       boot.loader.grub.enableCryptodisk = true;
       boot.loader.efi.efiSysMountPoint = "/boot/efi";
 
-      boot.initrd.secrets."/luks.key" = ./luks.key;
+      boot.initrd.secrets."/luks.key" = "/etc/nixos/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")
+    postBootCommands = ''
+      target.wait_for_text("Enter passphrase for")
+      target.send_chars("supersecret\n")
     '';
   };
 
   swraid = makeInstallerTest "swraid" {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda --"
           + " mklabel msdos"
           + " mkpart primary ext2 1M 100MB"  # /boot
@@ -1128,15 +1105,14 @@ in {
           "udevadm settle",
       )
     '';
-    preBootCommands = ''
-      machine.start()
-      machine.fail("dmesg | grep 'immediate safe mode'")
+    postBootCommands = ''
+      target.fail("dmesg | grep 'immediate safe mode'")
     '';
   };
 
   bcache = makeInstallerTest "bcache" {
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
           "flock /dev/vda parted --script /dev/vda --"
           + " mklabel msdos"
           + " mkpart primary ext2 1M 100MB"  # /boot
@@ -1165,7 +1141,7 @@ in {
     };
 
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
         + " mkpart primary ext2 1M 100MB"          # /boot
         + " mkpart primary linux-swap 100M 1024M"  # swap
@@ -1197,18 +1173,17 @@ in {
     '';
 
     enableOCR = true;
-    preBootCommands = ''
-      machine.start()
+    postBootCommands = ''
       # Enter it wrong once
-      machine.wait_for_text("enter passphrase for ")
-      machine.send_chars("wrong\n")
+      target.wait_for_text("enter passphrase for ")
+      target.send_chars("wrong\n")
       # Then enter it right.
-      machine.wait_for_text("enter passphrase for ")
-      machine.send_chars("password\n")
+      target.wait_for_text("enter passphrase for ")
+      target.send_chars("password\n")
     '';
 
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
         + " mkpart primary ext2 1M 100MB"          # /boot
         + " mkpart primary linux-swap 100M 1024M"  # swap
@@ -1235,7 +1210,7 @@ in {
     };
 
     createPartitions = ''
-      machine.succeed(
+      installer.succeed(
         "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
         + " mkpart primary ext2 1M 100MB"          # /boot
         + " mkpart primary linux-swap 100M 1024M"  # swap
@@ -1256,7 +1231,7 @@ in {
   # Test using labels to identify volumes in grub
   simpleLabels = makeInstallerTest "simpleLabels" {
     createPartitions = ''
-      machine.succeed(
+      installer.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",
@@ -1273,7 +1248,7 @@ in {
   simpleProvided = makeInstallerTest "simpleProvided" {
     createPartitions = ''
       uuid = "$(blkid -s UUID -o value /dev/vda2)"
-      machine.succeed(
+      installer.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",
@@ -1282,9 +1257,9 @@ in {
           "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(
+      installer.execute(f"ln -s ../../vda2 /dev/disk/by-uuid/{uuid}")
+      installer.execute("ln -s ../../vda4 /dev/disk/by-label/root")
+      installer.succeed(
           "mount /dev/disk/by-label/root /mnt",
           "mkdir /mnt/boot",
           f"mount /dev/disk/by-uuid/{uuid} /mnt/boot",
@@ -1296,7 +1271,7 @@ in {
   # Simple btrfs grub testing
   btrfsSimple = makeInstallerTest "btrfsSimple" {
     createPartitions = ''
-      machine.succeed(
+      installer.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",
@@ -1310,7 +1285,7 @@ in {
   # Test to see if we can detect /boot and /nix on subvolumes
   btrfsSubvols = makeInstallerTest "btrfsSubvols" {
     createPartitions = ''
-      machine.succeed(
+      installer.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",
@@ -1332,7 +1307,7 @@ in {
   # Test to see if we can detect default and aux subvolumes correctly
   btrfsSubvolDefault = makeInstallerTest "btrfsSubvolDefault" {
     createPartitions = ''
-      machine.succeed(
+      installer.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",
@@ -1358,7 +1333,7 @@ in {
   # Test to see if we can deal with subvols that need to be escaped in fstab
   btrfsSubvolEscape = makeInstallerTest "btrfsSubvolEscape" {
     createPartitions = ''
-      machine.succeed(
+      installer.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",
@@ -1385,7 +1360,7 @@ in {
 } // optionalAttrs systemdStage1 {
   stratisRoot = makeInstallerTest "stratisRoot" {
     createPartitions = ''
-      machine.succeed(
+      installer.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
@@ -1428,7 +1403,7 @@ in {
   in makeInstallerTest "gptAutoRoot" {
     disableFileSystems = true;
     createPartitions = ''
-      machine.succeed(
+      installer.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
diff --git a/nixos/tests/oddjobd.nix b/nixos/tests/oddjobd.nix
new file mode 100644
index 0000000000000..cc2d4079eebc9
--- /dev/null
+++ b/nixos/tests/oddjobd.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "oddjobd";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine = { ... } : {
+    environment.systemPackages = [
+      pkgs.oddjob
+    ];
+
+    programs.oddjobd.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("oddjobd.service")
+    machine.wait_for_file("/run/oddjobd.pid")
+
+    with subtest("send oddjob listall request"):
+      result = machine.succeed("oddjob_request -s com.redhat.oddjob -o /com/redhat/oddjob -i com.redhat.oddjob listall")
+      assert ('(service="com.redhat.oddjob",object="/com/redhat/oddjob",interface="com.redhat.oddjob",method="listall")' in result)
+  '';
+})
diff --git a/nixos/tests/pantheon.nix b/nixos/tests/pantheon.nix
index 14f92fa3af4a2..f8de2eb8061d1 100644
--- a/nixos/tests/pantheon.nix
+++ b/nixos/tests/pantheon.nix
@@ -50,7 +50,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
             machine.wait_until_succeeds(f"pgrep -f {i}")
         for i in ["gala", "io.elementary.wingpanel", "plank"]:
             machine.wait_for_window(i)
-        for i in ["io.elementary.gala.daemon@x11.service", "bamfdaemon.service", "io.elementary.files.xdg-desktop-portal.service"]:
+        for i in ["bamfdaemon.service", "io.elementary.files.xdg-desktop-portal.service"]:
             machine.wait_for_unit(i, "${user.name}")
 
     with subtest("Check if various environment variables are set"):
diff --git a/nixos/tests/silverbullet.nix b/nixos/tests/silverbullet.nix
new file mode 100644
index 0000000000000..e7e3cf5365583
--- /dev/null
+++ b/nixos/tests/silverbullet.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "silverbullet";
+  meta.maintainers = with lib.maintainers; [ aorith ];
+
+  nodes.simple = { ... }: {
+    services.silverbullet.enable = true;
+  };
+
+  nodes.configured = { pkgs, ... }: {
+    users.users.test.isNormalUser = true;
+    users.groups.test = { };
+
+    services.silverbullet = {
+      enable = true;
+      package = pkgs.silverbullet;
+      listenPort = 3001;
+      listenAddress = "localhost";
+      spaceDir = "/home/test/silverbullet";
+      user = "test";
+      group = "test";
+      envFile = pkgs.writeText "silverbullet.env" ''
+        SB_USER=user:password
+        SB_AUTH_TOKEN=test
+      '';
+      extraArgs = [ "--reindex" "--db /home/test/silverbullet/custom.db" ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    PORT = ${builtins.toString nodes.simple.services.silverbullet.listenPort}
+    ADDRESS = "${nodes.simple.services.silverbullet.listenAddress}"
+    SPACEDIR = "${nodes.simple.services.silverbullet.spaceDir}"
+    simple.wait_for_unit("silverbullet.service")
+    simple.wait_for_open_port(PORT)
+    simple.succeed(f"curl --max-time 5 -s -v -o /dev/null --fail http://{ADDRESS}:{PORT}/")
+    simple.succeed(f"test -d '{SPACEDIR}'")
+
+    PORT = ${builtins.toString nodes.configured.services.silverbullet.listenPort}
+    ADDRESS = "${nodes.configured.services.silverbullet.listenAddress}"
+    SPACEDIR = "${nodes.configured.services.silverbullet.spaceDir}"
+    configured.wait_for_unit("silverbullet.service")
+    configured.wait_for_open_port(PORT)
+    assert int(configured.succeed(f"curl --max-time 5 -s -o /dev/null -w '%{{http_code}}' -XPUT -d 'test' --fail http://{ADDRESS}:{PORT}/test.md -H'Authorization: Bearer test'")) == 200
+    assert int(configured.fail(f"curl --max-time 5 -s -o /dev/null -w '%{{http_code}}' -XPUT -d 'test' --fail http://{ADDRESS}:{PORT}/test.md -H'Authorization: Bearer wrong'")) == 401
+    configured.succeed(f"test -d '{SPACEDIR}'")
+  '';
+})
diff --git a/nixos/tests/soju.nix b/nixos/tests/soju.nix
new file mode 100644
index 0000000000000..23da36f7b3aba
--- /dev/null
+++ b/nixos/tests/soju.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+
+  user = "testuser";
+  pass = "hunter2";
+in
+{
+  name = "soju";
+  meta.maintainers = with lib.maintainers; [ Benjamin-L ];
+
+  nodes.machine = { ... }: {
+    services.soju = {
+      enable = true;
+      adminSocket.enable = true;
+      hostName = domain;
+      tlsCertificate = certs.${domain}.cert;
+      tlsCertificateKey = certs.${domain}.key;
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("soju")
+    machine.wait_for_file("/run/soju/admin")
+
+    machine.succeed("sojuctl user create -username ${user} -password ${pass}")
+  '';
+})
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index 5ffdf180d5e3f..a57d66f82eac9 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -691,9 +691,9 @@ in {
     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")
+        machine.succeed("echo dbus-broker.service > /run/nixos/start-list")
         out = switch_to_specialisation("${machine}", "modifiedSystemConf")
-        assert_contains(out, "starting the following units: dbus.service\n")
+        assert_contains(out, "starting the following units: dbus-broker.service\n")
 
     with subtest("fstab mounts"):
         switch_to_specialisation("${machine}", "")
@@ -732,7 +732,7 @@ in {
         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_contains(out, "reloading the following units: dbus-broker.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:")
@@ -740,7 +740,7 @@ in {
         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_contains(out, "reloading the following units: dbus-broker.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:")
@@ -751,7 +751,7 @@ in {
         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_contains(out, "reloading the following units: dbus-broker.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")
@@ -760,7 +760,7 @@ in {
         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_contains(out, "reloading the following units: dbus-broker.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:")
@@ -781,7 +781,7 @@ in {
         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_contains(out, "reloading the following units: dbus-broker.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")
@@ -858,7 +858,7 @@ in {
         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_contains(out, "reloading the following units: dbus-broker.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")