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/from_md/release-notes/rl-2305.section.xml16
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md4
-rw-r--r--nixos/modules/module-list.nix6
-rw-r--r--nixos/modules/programs/firefox.nix106
-rw-r--r--nixos/modules/programs/fzf.nix14
-rw-r--r--nixos/modules/programs/rog-control-center.nix29
-rw-r--r--nixos/modules/programs/skim.nix30
-rw-r--r--nixos/modules/services/hardware/asusd.nix114
-rw-r--r--nixos/modules/services/hardware/supergfxd.nix38
-rw-r--r--nixos/modules/services/home-automation/zigbee2mqtt.nix5
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix11
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix (renamed from nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix)10
-rw-r--r--nixos/modules/services/monitoring/unpoller.nix (renamed from nixos/modules/services/monitoring/unifi-poller.nix)26
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix27
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix4
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix31
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix11
-rw-r--r--nixos/modules/system/boot/networkd.nix2
-rw-r--r--nixos/modules/virtualisation/lxd.nix3
-rw-r--r--nixos/tests/grocy.nix26
-rw-r--r--nixos/tests/prometheus-exporters.nix8
-rw-r--r--nixos/tests/web-apps/mastodon.nix26
22 files changed, 456 insertions, 91 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
index e54bec81b2f3f..0fd0382998c20 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2305.section.xml
@@ -121,6 +121,15 @@
       </listitem>
       <listitem>
         <para>
+          <literal>services.mastodon</literal> gained a tootctl wrapped
+          named <literal>mastodon-tootctl</literal> similar to
+          <literal>nextcloud-occ</literal> which can be executed from
+          any user and switches to the configured mastodon user with
+          sudo and sources the environment variables.
+        </para>
+      </listitem>
+      <listitem>
+        <para>
           A new <literal>virtualisation.rosetta</literal> module was
           added to allow running <literal>x86_64</literal> binaries
           through
@@ -139,6 +148,13 @@
           the Nix store.
         </para>
       </listitem>
+      <listitem>
+        <para>
+          The <literal>unifi-poller</literal> package and corresponding
+          NixOS module have been renamed to <literal>unpoller</literal>
+          to match upstream.
+        </para>
+      </listitem>
     </itemizedlist>
   </section>
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index 0e5b97c898460..39550d44733ad 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -41,6 +41,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The module for the application firewall `opensnitch` got the ability to configure rules. Available as [services.opensnitch.rules](#opt-services.opensnitch.rules)
 
+- `services.mastodon` gained a tootctl wrapped named `mastodon-tootctl` similar to `nextcloud-occ` which can be executed from any user and switches to the configured mastodon user with sudo and sources the environment variables.
+
 - A new `virtualisation.rosetta` module was added to allow running `x86_64` binaries through [Rosetta](https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment) inside virtualised NixOS guests on Apple silicon. This feature works by default with the [UTM](https://docs.getutm.app/) virtualisation [package](https://search.nixos.org/packages?channel=unstable&show=utm&from=0&size=1&sort=relevance&type=packages&query=utm).
 
 - Resilio sync secret keys can now be provided using a secrets file at runtime, preventing these secrets from ending up in the Nix store.
+
+- The `unifi-poller` package and corresponding NixOS module have been renamed to `unpoller` to match upstream.
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 59d6cf165f180..9faa58409b19d 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -211,10 +211,12 @@
   ./programs/plotinus.nix
   ./programs/proxychains.nix
   ./programs/qt5ct.nix
+  ./programs/rog-control-center.nix
   ./programs/rust-motd.nix
   ./programs/screen.nix
   ./programs/sedutil.nix
   ./programs/seahorse.nix
+  ./programs/skim.nix
   ./programs/slock.nix
   ./programs/shadow.nix
   ./programs/spacefm.nix
@@ -450,6 +452,7 @@
   ./services/hardware/acpid.nix
   ./services/hardware/actkbd.nix
   ./services/hardware/argonone.nix
+  ./services/hardware/asusd.nix
   ./services/hardware/auto-cpufreq.nix
   ./services/hardware/bluetooth.nix
   ./services/hardware/bolt.nix
@@ -477,6 +480,7 @@
   ./services/hardware/sane_extra_backends/brscan5.nix
   ./services/hardware/sane_extra_backends/dsseries.nix
   ./services/hardware/spacenavd.nix
+  ./services/hardware/supergfxd.nix
   ./services/hardware/tcsd.nix
   ./services/hardware/tlp.nix
   ./services/hardware/thinkfan.nix
@@ -724,7 +728,7 @@
   ./services/monitoring/thanos.nix
   ./services/monitoring/tremor-rs.nix
   ./services/monitoring/tuptime.nix
-  ./services/monitoring/unifi-poller.nix
+  ./services/monitoring/unpoller.nix
   ./services/monitoring/ups.nix
   ./services/monitoring/uptime.nix
   ./services/monitoring/vmagent.nix
diff --git a/nixos/modules/programs/firefox.nix b/nixos/modules/programs/firefox.nix
index 76e6c1a553f3a..dfd912cdf5c11 100644
--- a/nixos/modules/programs/firefox.nix
+++ b/nixos/modules/programs/firefox.nix
@@ -5,6 +5,8 @@ with lib;
 let
   cfg = config.programs.firefox;
 
+  nmh = cfg.nativeMessagingHosts;
+
   policyFormat = pkgs.formats.json { };
 
   organisationInfo = ''
@@ -15,15 +17,15 @@ let
     given control of your browser, unless of course they also control your
     NixOS configuration.
   '';
-
-in {
+in
+{
   options.programs.firefox = {
     enable = mkEnableOption (mdDoc "the Firefox web browser");
 
     package = mkOption {
-      description = mdDoc "Firefox package to use.";
       type = types.package;
       default = pkgs.firefox;
+      description = mdDoc "Firefox package to use.";
       defaultText = literalExpression "pkgs.firefox";
       relatedPackages = [
         "firefox"
@@ -31,12 +33,12 @@ in {
         "firefox-bin"
         "firefox-devedition-bin"
         "firefox-esr"
-        "firefox-esr-wayland"
-        "firefox-wayland"
       ];
     };
 
     policies = mkOption {
+      type = policyFormat.type;
+      default = { };
       description = mdDoc ''
         Group policies to install.
 
@@ -48,43 +50,97 @@ in {
 
         ${organisationInfo}
       '';
-      type = policyFormat.type;
-      default = {};
     };
 
     preferences = mkOption {
+      type = with types; attrsOf (oneOf [ bool int string ]);
+      default = { };
       description = mdDoc ''
-        Preferences to set from `about://config`.
+        Preferences to set from `about:config`.
 
         Some of these might be able to be configured more ergonomically
         using policies.
 
         ${organisationInfo}
       '';
-      type = with types; attrsOf (oneOf [ bool int string ]);
-      default = {};
+    };
+
+    preferencesStatus = mkOption {
+      type = types.enum [ "default" "locked" "user" "clear" ];
+      default = "locked";
+      description = mdDoc ''
+        The status of `firefox.preferences`.
+
+        `status` can assume the following values:
+        - `"default"`: Preferences appear as default.
+        - `"locked"`: Preferences appear as default and can't be changed.
+        - `"user"`: Preferences appear as changed.
+        - `"clear"`: Value has no effect. Resets to factory defaults on each startup.
+      '';
+    };
+
+    autoConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = mdDoc ''
+        AutoConfig files can be used to set and lock preferences that are not covered
+        by the policies.json for Mac and Linux. This method can be used to automatically
+        change user preferences or prevent the end user from modifiying specific
+        preferences by locking them. More info can be found in https://support.mozilla.org/en-US/kb/customizing-firefox-using-autoconfig.
+      '';
+    };
+
+    nativeMessagingHosts = mapAttrs (_: v: mkEnableOption (mdDoc v)) {
+      browserpass = "Browserpass support";
+      bukubrow = "Bukubrow support";
+      ff2mpv = "ff2mpv support";
+      fxCast = "fx_cast support";
+      gsconnect = "GSConnect support";
+      jabref = "JabRef support";
+      passff = "PassFF support";
+      tridactyl = "Tridactyl support";
+      ugetIntegrator = "Uget Integrator support";
     };
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ cfg.package ];
+    environment.systemPackages = [
+      (cfg.package.override {
+        extraPrefs = cfg.autoConfig;
+        extraNativeMessagingHosts = with pkgs; optionals nmh.ff2mpv [
+          ff2mpv
+        ] ++ optionals nmh.gsconnect [
+          gnomeExtensions.gsconnect
+        ] ++ optionals nmh.jabref [
+          jabref
+        ] ++ optionals nmh.passff [
+          passff-host
+        ];
+      })
+    ];
+
+    nixpkgs.config.firefox = {
+      enableBrowserpass = nmh.browserpass;
+      enableBukubrow = nmh.bukubrow;
+      enableTridactylNative = nmh.tridactyl;
+      enableUgetIntegrator = nmh.ugetIntegrator;
+      enableFXCastBridge = nmh.fxCast;
+    };
 
-    environment.etc."firefox/policies/policies.json".source =
-      let policiesJSON =
-        policyFormat.generate
-        "firefox-policies.json"
-        { inherit (cfg) policies; };
-      in mkIf (cfg.policies != {}) "${policiesJSON}";
+    environment.etc =
+      let
+        policiesJSON = policyFormat.generate "firefox-policies.json" { inherit (cfg) policies; };
+      in
+      mkIf (cfg.policies != { }) {
+        "firefox/policies/policies.json".source = "${policiesJSON}";
+      };
 
     # Preferences are converted into a policy
-    programs.firefox.policies =
-      mkIf (cfg.preferences != {})
-      {
-        Preferences = (mapAttrs (name: value: {
-          Value = value;
-          Status = "locked";
-        }) cfg.preferences);
-      };
+    programs.firefox.policies = mkIf (cfg.preferences != { }) {
+      Preferences = (mapAttrs
+        (_: value: { Value = value; Status = cfg.preferencesStatus; })
+        cfg.preferences);
+    };
   };
 
   meta.maintainers = with maintainers; [ danth ];
diff --git a/nixos/modules/programs/fzf.nix b/nixos/modules/programs/fzf.nix
index 0452bf4262276..eda4eacde4ac9 100644
--- a/nixos/modules/programs/fzf.nix
+++ b/nixos/modules/programs/fzf.nix
@@ -5,18 +5,8 @@ let
 in {
   options = {
     programs.fzf = {
-      fuzzyCompletion = mkOption {
-        type = types.bool;
-        description = lib.mdDoc "Whether to use fzf for fuzzy completion";
-        default = false;
-        example = true;
-      };
-      keybindings = mkOption {
-        type = types.bool;
-        description = lib.mdDoc "Whether to set up fzf keybindings";
-        default = false;
-        example = true;
-      };
+      fuzzyCompletion = mkEnableOption (mdDoc "fuzzy completion with fzf");
+      keybindings = mkEnableOption (mdDoc "fzf keybindings");
     };
   };
   config = {
diff --git a/nixos/modules/programs/rog-control-center.nix b/nixos/modules/programs/rog-control-center.nix
new file mode 100644
index 0000000000000..4aef5143ac7ff
--- /dev/null
+++ b/nixos/modules/programs/rog-control-center.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.rog-control-center;
+in
+{
+  options = {
+    programs.rog-control-center = {
+      enable = lib.mkEnableOption (lib.mdDoc "the rog-control-center application");
+
+      autoStart = lib.mkOption {
+        default = false;
+        type = lib.types.bool;
+        description = lib.mdDoc "Whether rog-control-center should be started automatically.";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [
+      pkgs.asusctl
+      (lib.mkIf cfg.autoStart (pkgs.makeAutostartItem { name = "rog-control-center"; package = pkgs.asusctl; }))
+    ];
+
+    services.asusd.enable = true;
+  };
+
+  meta.maintainers = pkgs.asusctl.meta.maintainers;
+}
diff --git a/nixos/modules/programs/skim.nix b/nixos/modules/programs/skim.nix
new file mode 100644
index 0000000000000..1333cdd30ab23
--- /dev/null
+++ b/nixos/modules/programs/skim.nix
@@ -0,0 +1,30 @@
+{ pkgs, config, lib, ... }:
+let
+  inherit (lib) mdDoc mkEnableOption mkPackageOption optional optionalString;
+  cfg = config.programs.skim;
+in
+{
+  options = {
+    programs.skim = {
+      fuzzyCompletion = mkEnableOption (mdDoc "fuzzy Completion with skim");
+      keybindings = mkEnableOption (mdDoc "skim keybindings");
+      package = mkPackageOption pkgs "skim" {};
+    };
+  };
+
+  config = {
+    environment.systemPackages = optional (cfg.keybindings || cfg.fuzzyCompletion) cfg.package;
+
+    programs.bash.interactiveShellInit = optionalString cfg.fuzzyCompletion ''
+      source ${cfg.package}/share/skim/completion.bash
+    '' + optionalString cfg.keybindings ''
+      source ${cfg.package}/share/skim/key-bindings.bash
+    '';
+
+    programs.zsh.interactiveShellInit = optionalString cfg.fuzzyCompletion ''
+      source ${cfg.package}/share/skim/completion.zsh
+    '' + optionalString cfg.keybindings ''
+      source ${cfg.package}/share/skim/key-bindings.zsh
+    '';
+  };
+}
diff --git a/nixos/modules/services/hardware/asusd.nix b/nixos/modules/services/hardware/asusd.nix
new file mode 100644
index 0000000000000..f0751c4402516
--- /dev/null
+++ b/nixos/modules/services/hardware/asusd.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.asusd;
+  json = pkgs.formats.json { };
+  toml = pkgs.formats.toml { };
+in
+{
+  options = {
+    services.asusd = {
+      enable = lib.mkEnableOption (lib.mdDoc "the asusd service for ASUS ROG laptops");
+
+      enableUserService = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Activate the asusd-user service.
+        '';
+      };
+
+      animeConfig = lib.mkOption {
+        type = json.type;
+        default = { };
+        description = lib.mdDoc ''
+          The content of /etc/asusd/anime.conf.
+          See https://asus-linux.org/asusctl/#anime-control.
+        '';
+      };
+
+      asusdConfig = lib.mkOption {
+        type = json.type;
+        default = { };
+        description = lib.mdDoc ''
+          The content of /etc/asusd/asusd.conf.
+          See https://asus-linux.org/asusctl/.
+        '';
+      };
+
+      auraConfig = lib.mkOption {
+        type = json.type;
+        default = { };
+        description = lib.mdDoc ''
+          The content of /etc/asusd/aura.conf.
+          See https://asus-linux.org/asusctl/#led-keyboard-control.
+        '';
+      };
+
+      profileConfig = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = "";
+        description = lib.mdDoc ''
+          The content of /etc/asusd/profile.conf.
+          See https://asus-linux.org/asusctl/#profiles.
+        '';
+      };
+
+      ledModesConfig = lib.mkOption {
+        type = lib.types.nullOr toml.type;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/asusd-ledmodes.toml. Leave `null` to use default settings.
+          See https://asus-linux.org/asusctl/#led-keyboard-control.
+        '';
+      };
+
+      userLedModesConfig = lib.mkOption {
+        type = lib.types.nullOr toml.type;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/asusd/asusd-user-ledmodes.toml.
+          See https://asus-linux.org/asusctl/#led-keyboard-control.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.asusctl ];
+
+    environment.etc =
+      let
+        maybeConfig = name: cfg: lib.mkIf (cfg != { }) {
+          source = json.generate name cfg;
+          mode = "0644";
+        };
+      in
+      {
+        "asusd/anime.conf" = maybeConfig "anime.conf" cfg.animeConfig;
+        "asusd/asusd.conf" = maybeConfig "asusd.conf" cfg.asusdConfig;
+        "asusd/aura.conf" = maybeConfig "aura.conf" cfg.auraConfig;
+        "asusd/profile.conf" = lib.mkIf (cfg.profileConfig != null) {
+          source = pkgs.writeText "profile.conf" cfg.profileConfig;
+          mode = "0644";
+        };
+        "asusd/asusd-ledmodes.toml" = {
+          source =
+            if cfg.ledModesConfig == null
+            then "${pkgs.asusctl}/share/asusd/data/asusd-ledmodes.toml"
+            else toml.generate "asusd-ledmodes.toml" cfg.ledModesConfig;
+          mode = "0644";
+        };
+      };
+
+    services.dbus.enable = true;
+    systemd.packages = [ pkgs.asusctl ];
+    services.dbus.packages = [ pkgs.asusctl ];
+    services.udev.packages = [ pkgs.asusctl ];
+    services.supergfxd.enable = true;
+
+    systemd.user.services.asusd-user.enable = cfg.enableUserService;
+  };
+
+  meta.maintainers = pkgs.asusctl.meta.maintainers;
+}
diff --git a/nixos/modules/services/hardware/supergfxd.nix b/nixos/modules/services/hardware/supergfxd.nix
new file mode 100644
index 0000000000000..abb6bedb98ff3
--- /dev/null
+++ b/nixos/modules/services/hardware/supergfxd.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.supergfxd;
+  ini = pkgs.formats.ini { };
+in
+{
+  options = {
+    services.supergfxd = {
+      enable = lib.mkEnableOption (lib.mdDoc "Enable the supergfxd service");
+
+      settings = lib.mkOption {
+        type = lib.types.nullOr ini.type;
+        default = null;
+        description = lib.mdDoc ''
+          The content of /etc/supergfxd.conf.
+          See https://gitlab.com/asus-linux/supergfxctl/#config-options-etcsupergfxdconf.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.supergfxctl ];
+
+    environment.etc."supergfxd.conf" = lib.mkIf (cfg.settings != null) (ini.generate "supergfxd.conf" cfg.settings);
+
+    services.dbus.enable = true;
+
+    systemd.packages = [ pkgs.supergfxctl ];
+    systemd.services.supergfxd.wantedBy = [ "multi-user.target" ];
+
+    services.dbus.packages = [ pkgs.supergfxctl ];
+    services.udev.packages = [ pkgs.supergfxctl ];
+  };
+
+  meta.maintainers = pkgs.supergfxctl.meta.maintainers;
+}
diff --git a/nixos/modules/services/home-automation/zigbee2mqtt.nix b/nixos/modules/services/home-automation/zigbee2mqtt.nix
index 71f6e7a258404..796de3a491e49 100644
--- a/nixos/modules/services/home-automation/zigbee2mqtt.nix
+++ b/nixos/modules/services/home-automation/zigbee2mqtt.nix
@@ -119,9 +119,8 @@ in
         ];
         SystemCallArchitectures = "native";
         SystemCallFilter = [
-          "@system-service"
-          "~@privileged"
-          "~@resources"
+          "@system-service @pkey"
+          "~@privileged @resources"
         ];
         UMask = "0077";
       };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 22b78981b2cce..2451f46ba7d75 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -73,7 +73,7 @@ let
     "tor"
     "unbound"
     "unifi"
-    "unifi-poller"
+    "unpoller"
     "v2ray"
     "varnish"
     "wireguard"
@@ -230,6 +230,10 @@ in
   options.services.prometheus.exporters = mkOption {
     type = types.submodule {
       options = (mkSubModules);
+      imports = [
+        ../../../misc/assertions.nix
+        (lib.mkRenamedOptionModule [ "unifi-poller" ] [ "unpoller" ])
+      ];
     };
     description = lib.mdDoc "Prometheus exporter configuration";
     default = {};
@@ -293,13 +297,14 @@ in
         Please specify either 'services.prometheus.exporters.sql.configuration' or
           'services.prometheus.exporters.sql.configFile'
       '';
-    } ] ++ (flip map (attrNames cfg) (exporter: {
+    } ] ++ (flip map (attrNames exporterOpts) (exporter: {
       assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
       message = ''
         The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless
         `openFirewall' is set to `true'!
       '';
-    }));
+    })) ++ config.services.prometheus.exporters.assertions;
+    warnings = config.services.prometheus.exporters.warnings;
   }] ++ [(mkIf config.services.minio.enable {
     services.prometheus.exporters.minio.minioAddress  = mkDefault "http://localhost:9000";
     services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix b/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix
index 35de31df88e6d..5cd1e2c65e906 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/unifi-poller.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/unpoller.nix
@@ -3,9 +3,9 @@
 with lib;
 
 let
-  cfg = config.services.prometheus.exporters.unifi-poller;
+  cfg = config.services.prometheus.exporters.unpoller;
 
-  configFile = pkgs.writeText "prometheus-unifi-poller-exporter.json" (generators.toJSON {} {
+  configFile = pkgs.writeText "prometheus-unpoller-exporter.json" (generators.toJSON {} {
     poller = { inherit (cfg.log) debug quiet; };
     unifi = { inherit (cfg) controllers; };
     influxdb.disable = true;
@@ -21,8 +21,8 @@ in {
   port = 9130;
 
   extraOpts = {
-    inherit (options.services.unifi-poller.unifi) controllers;
-    inherit (options.services.unifi-poller) loki;
+    inherit (options.services.unpoller.unifi) controllers;
+    inherit (options.services.unpoller) loki;
     log = {
       debug = mkEnableOption (lib.mdDoc "debug logging including line numbers, high resolution timestamps, per-device logs.");
       quiet = mkEnableOption (lib.mdDoc "startup and error logs only.");
@@ -31,7 +31,7 @@ in {
   };
 
   serviceOpts.serviceConfig = {
-    ExecStart = "${pkgs.unifi-poller}/bin/unpoller --config ${configFile}";
+    ExecStart = "${pkgs.unpoller}/bin/unpoller --config ${configFile}";
     DynamicUser = false;
   };
 }
diff --git a/nixos/modules/services/monitoring/unifi-poller.nix b/nixos/modules/services/monitoring/unpoller.nix
index b30e28a3ecc93..f0ced5513d64b 100644
--- a/nixos/modules/services/monitoring/unifi-poller.nix
+++ b/nixos/modules/services/monitoring/unpoller.nix
@@ -3,15 +3,19 @@
 with lib;
 
 let
-  cfg = config.services.unifi-poller;
+  cfg = config.services.unpoller;
 
-  configFile = pkgs.writeText "unifi-poller.json" (generators.toJSON {} {
+  configFile = pkgs.writeText "unpoller.json" (generators.toJSON {} {
     inherit (cfg) poller influxdb loki prometheus unifi;
   });
 
 in {
-  options.services.unifi-poller = {
-    enable = mkEnableOption (lib.mdDoc "unifi-poller");
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "unifi-poller" ] [ "services" "unpoller" ])
+  ];
+
+  options.services.unpoller = {
+    enable = mkEnableOption (lib.mdDoc "unpoller");
 
     poller = {
       debug = mkOption {
@@ -86,8 +90,8 @@ in {
       };
       pass = mkOption {
         type = types.path;
-        default = pkgs.writeText "unifi-poller-influxdb-default.password" "unifipoller";
-        defaultText = literalExpression "unifi-poller-influxdb-default.password";
+        default = pkgs.writeText "unpoller-influxdb-default.password" "unifipoller";
+        defaultText = literalExpression "unpoller-influxdb-default.password";
         description = lib.mdDoc ''
           Path of a file containing the password for influxdb.
           This file needs to be readable by the unifi-poller user.
@@ -135,8 +139,8 @@ in {
       };
       pass = mkOption {
         type = types.path;
-        default = pkgs.writeText "unifi-poller-loki-default.password" "";
-        defaultText = "unifi-poller-influxdb-default.password";
+        default = pkgs.writeText "unpoller-loki-default.password" "";
+        defaultText = "unpoller-influxdb-default.password";
         description = lib.mdDoc ''
           Path of a file containing the password for Loki.
           This file needs to be readable by the unifi-poller user.
@@ -184,8 +188,8 @@ in {
         };
         pass = mkOption {
           type = types.path;
-          default = pkgs.writeText "unifi-poller-unifi-default.password" "unifi";
-          defaultText = literalExpression "unifi-poller-unifi-default.password";
+          default = pkgs.writeText "unpoller-unifi-default.password" "unifi";
+          defaultText = literalExpression "unpoller-unifi-default.password";
           description = lib.mdDoc ''
             Path of a file containing the password for the unifi service user.
             This file needs to be readable by the unifi-poller user.
@@ -303,7 +307,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.unifi-poller}/bin/unifi-poller --config ${configFile}";
+        ExecStart = "${pkgs.unpoller}/bin/unpoller --config ${configFile}";
         Restart = "always";
         PrivateTmp = true;
         ProtectHome = true;
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index a221186adf64c..8122c24494912 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -92,12 +92,18 @@ let
       ] else []
     ) env))));
 
-  mastodonEnv = pkgs.writeShellScriptBin "mastodon-env" ''
+  mastodonTootctl = pkgs.writeShellScriptBin "mastodon-tootctl" ''
+    #! ${pkgs.runtimeShell}
     set -a
     export RAILS_ROOT="${cfg.package}"
     source "${envFile}"
     source /var/lib/mastodon/.secrets_env
-    eval -- "\$@"
+
+    sudo=exec
+    if [[ "$USER" != ${cfg.user} ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env'
+    fi
+    $sudo ${cfg.package}/bin/tootctl "$@"
   '';
 
 in {
@@ -133,15 +139,10 @@ in {
         description = lib.mdDoc ''
           User under which mastodon runs. If it is set to "mastodon",
           that user will be created, otherwise it should be set to the
-          name of a user created elsewhere.  In both cases,
-          `mastodon` and a package containing only
-          the shell script `mastodon-env` will be added to
-          the user's package set. To run a command from
-          `mastodon` such as `tootctl`
-          with the environment configured by this module use
-          `mastodon-env`, as in:
-
-          `mastodon-env tootctl accounts create newuser --email newuser@example.com`
+          name of a user created elsewhere.
+          In both cases, the `mastodon` package will be added to the user's package set
+          and a tootctl wrapper to system packages that switches to the configured account
+          and load the right environment.
         '';
         type = lib.types.str;
         default = "mastodon";
@@ -485,6 +486,8 @@ in {
       }
     ];
 
+    environment.systemPackages = [ mastodonTootctl ];
+
     systemd.services.mastodon-init-dirs = {
       script = ''
         umask 077
@@ -704,7 +707,7 @@ in {
           inherit (cfg) group;
         };
       })
-      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package mastodonEnv pkgs.imagemagick ])
+      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package pkgs.imagemagick ])
     ];
 
     users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user;
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index 25de29554b1eb..8e6a44428fce1 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -74,6 +74,10 @@ in
           name = mkDefault "Mint-X-Dark";
           package = mkDefault pkgs.cinnamon.mint-x-icons;
         };
+        cursorTheme = mkIf (notExcluded pkgs.cinnamon.mint-cursor-themes) {
+          name = mkDefault "Bibata-Modern-Classic";
+          package = mkDefault pkgs.cinnamon.mint-cursor-themes;
+        };
       };
       services.xserver.displayManager.sessionCommands = ''
         if test "$XDG_CURRENT_DESKTOP" = "Cinnamon"; then
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
index 00fa8af71dc54..4456374cc569e 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
@@ -11,6 +11,7 @@ let
   theme = cfg.theme.package;
   icons = cfg.iconTheme.package;
   font = cfg.font.package;
+  cursors = cfg.cursorTheme.package;
 
   slickGreeterConf = writeText "slick-greeter.conf" ''
     [Greeter]
@@ -18,6 +19,8 @@ let
     theme-name=${cfg.theme.name}
     icon-theme-name=${cfg.iconTheme.name}
     font-name=${cfg.font.name}
+    cursor-theme-name=${cfg.cursorTheme.name}
+    cursor-theme-size=${toString cfg.cursorTheme.size}
     draw-user-backgrounds=${boolToString cfg.draw-user-backgrounds}
     ${cfg.extraConfig}
   '';
@@ -84,6 +87,33 @@ in
         };
       };
 
+      cursorTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome.adwaita-icon-theme;
+          defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme";
+          description = lib.mdDoc ''
+            The package path that contains the cursor theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = lib.mdDoc ''
+            Name of the cursor theme to use for the lightdm-slick-greeter.
+          '';
+        };
+
+        size = mkOption {
+          type = types.int;
+          default = 24;
+          description = lib.mdDoc ''
+            Size of the cursor theme to use for the lightdm-slick-greeter.
+          '';
+        };
+      };
+
       draw-user-backgrounds = mkEnableOption (lib.mdDoc "draw user backgrounds");
 
       extraConfig = mkOption {
@@ -107,6 +137,7 @@ in
     };
 
     environment.systemPackages = [
+      cursors
       icons
       theme
     ];
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 673655f20ee84..701d242abc154 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -73,6 +73,15 @@ in
       '';
     };
 
+    ignoreEmptyHostKeys = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Allow leaving {option}`config.boot.initrd.network.ssh` empty,
+        to deploy ssh host keys out of band.
+      '';
+    };
+
     authorizedKeys = mkOption {
       type = types.listOf types.str;
       default = config.users.users.root.openssh.authorizedKeys.keys;
@@ -141,7 +150,7 @@ in
       }
 
       {
-        assertion = cfg.hostKeys != [];
+        assertion = (cfg.hostKeys != []) || cfg.ignoreEmptyHostKeys;
         message = ''
           You must now pre-generate the host keys for initrd SSH.
           See the boot.initrd.network.ssh.hostKeys documentation
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 28abf820ec097..5f40ee9c08dc7 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -928,6 +928,8 @@ let
       type = types.bool;
       description = lib.mdDoc ''
         Whether to manage network configuration using {command}`systemd-network`.
+
+        This also enables {option}`systemd.networkd.enable`.
       '';
     };
 
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index e3eb519b7dddf..c06716e5eb605 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -140,7 +140,8 @@ in {
       ];
       documentation = [ "man:lxd(1)" ];
 
-      path = optional cfg.zfsSupport config.boot.zfs.package;
+      path = [ pkgs.util-linux ]
+        ++ optional cfg.zfsSupport config.boot.zfs.package;
 
       serviceConfig = {
         ExecStart = "@${cfg.package}/bin/lxd lxd --group lxd";
diff --git a/nixos/tests/grocy.nix b/nixos/tests/grocy.nix
index fe0ddd341486b..48bbc9f7d3fa2 100644
--- a/nixos/tests/grocy.nix
+++ b/nixos/tests/grocy.nix
@@ -14,6 +14,9 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   };
 
   testScript = ''
+    from base64 import b64encode
+    from urllib.parse import quote
+
     machine.start()
     machine.wait_for_open_port(80)
     machine.wait_for_unit("multi-user.target")
@@ -42,6 +45,29 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     machine.succeed("curl -sSI http://localhost/api/tasks 2>&1 | grep '401 Unauthorized'")
 
+    file_name = "test.txt"
+    file_name_base64 = b64encode(file_name.encode('ascii')).decode('ascii')
+    file_name_base64_urlencode = quote(file_name_base64)
+
+    machine.succeed(
+        f"echo Sample equipment manual > /tmp/{file_name}"
+    )
+
+    machine.succeed(
+        f"curl -sSf -X 'PUT' -b 'grocy_session={cookie}' "
+        + f" 'http://localhost/api/files/equipmentmanuals/{file_name_base64_urlencode}' "
+        + "  --header 'Accept: */*' "
+        + "  --header 'Content-Type: application/octet-stream' "
+        + f" --data-binary '@/tmp/{file_name}' "
+    )
+
+    machine.succeed(
+        f"curl -sSf -X 'GET' -b 'grocy_session={cookie}' "
+        + f" 'http://localhost/api/files/equipmentmanuals/{file_name_base64_urlencode}' "
+        + "  --header 'Accept: application/octet-stream' "
+        + f" | cmp /tmp/{file_name}"
+    )
+
     machine.shutdown()
   '';
 })
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index d91fc52f1cb45..cdf666378fa37 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -1244,15 +1244,15 @@ let
       '';
     };
 
-    unifi-poller = {
-      nodeName = "unifi_poller";
+    unpoller = {
+      nodeName = "unpoller";
       exporterConfig.enable = true;
       exporterConfig.controllers = [{ }];
       exporterTest = ''
-        wait_for_unit("prometheus-unifi-poller-exporter.service")
+        wait_for_unit("prometheus-unpoller-exporter.service")
         wait_for_open_port(9130)
         succeed(
-            "curl -sSf localhost:9130/metrics | grep 'unifipoller_build_info{.\\+} 1'"
+            "curl -sSf localhost:9130/metrics | grep 'unpoller_build_info{.\\+} 1'"
         )
       '';
     };
diff --git a/nixos/tests/web-apps/mastodon.nix b/nixos/tests/web-apps/mastodon.nix
index d3d53dc319469..f10cb8cdc6771 100644
--- a/nixos/tests/web-apps/mastodon.nix
+++ b/nixos/tests/web-apps/mastodon.nix
@@ -104,24 +104,24 @@ in
 
     # Simple check tootctl commands
     # Check Mastodon version
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl version' | grep '${pkgs.mastodon.version}'")
+    server.succeed("mastodon-tootctl version | grep '${pkgs.mastodon.version}'")
 
     # Manage accounts
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl email_domain_blocks add example.com'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl email_domain_blocks list' | grep 'example.com'")
-    server.fail("su - mastodon -s /bin/sh -c 'mastodon-env tootctl email_domain_blocks list' | grep 'mastodon.local'")
-    server.fail("su - mastodon -s /bin/sh -c 'mastodon-env tootctl accounts create alice --email=alice@example.com'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl email_domain_blocks remove example.com'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl accounts create bob --email=bob@example.com'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl accounts approve bob'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl accounts delete bob'")
+    server.succeed("mastodon-tootctl email_domain_blocks add example.com")
+    server.succeed("mastodon-tootctl email_domain_blocks list | grep example.com")
+    server.fail("mastodon-tootctl email_domain_blocks list | grep mastodon.local")
+    server.fail("mastodon-tootctl accounts create alice --email=alice@example.com")
+    server.succeed("mastodon-tootctl email_domain_blocks remove example.com")
+    server.succeed("mastodon-tootctl accounts create bob --email=bob@example.com")
+    server.succeed("mastodon-tootctl accounts approve bob")
+    server.succeed("mastodon-tootctl accounts delete bob")
 
     # Manage IP access
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl ip_blocks add 192.168.0.0/16 --severity=no_access'")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl ip_blocks export' | grep '192.168.0.0/16'")
-    server.fail("su - mastodon -s /bin/sh -c 'mastodon-env tootctl ip_blocks export' | grep '172.16.0.0/16'")
+    server.succeed("mastodon-tootctl ip_blocks add 192.168.0.0/16 --severity=no_access")
+    server.succeed("mastodon-tootctl ip_blocks export | grep 192.168.0.0/16")
+    server.fail("mastodon-tootctl ip_blocks export | grep 172.16.0.0/16")
     client.fail("curl --fail https://mastodon.local/about")
-    server.succeed("su - mastodon -s /bin/sh -c 'mastodon-env tootctl ip_blocks remove 192.168.0.0/16'")
+    server.succeed("mastodon-tootctl ip_blocks remove 192.168.0.0/16")
     client.succeed("curl --fail https://mastodon.local/about")
 
     server.shutdown()