about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/development/replace-modules.section.md11
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md4
-rw-r--r--nixos/modules/config/no-x-libs.nix2
-rw-r--r--nixos/modules/module-list.nix3
-rw-r--r--nixos/modules/programs/java.nix24
-rw-r--r--nixos/modules/programs/waybar.nix9
-rw-r--r--nixos/modules/services/backup/btrbk.nix57
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix2
-rw-r--r--nixos/modules/services/development/gemstash.nix103
-rw-r--r--nixos/modules/services/misc/gitlab.nix218
-rw-r--r--nixos/modules/services/misc/jellyseerr.nix62
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix2
-rw-r--r--nixos/modules/services/networking/murmur.nix30
-rw-r--r--nixos/modules/services/networking/networkd-dispatcher.nix87
-rw-r--r--nixos/modules/services/web-apps/coder.nix217
-rw-r--r--nixos/modules/services/x11/desktop-managers/phosh.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix1
-rw-r--r--nixos/modules/system/boot/systemd.nix2
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix4
-rw-r--r--nixos/tests/all-tests.nix4
-rw-r--r--nixos/tests/btrbk-doas.nix114
-rw-r--r--nixos/tests/coder.nix24
-rw-r--r--nixos/tests/docker-tools.nix54
-rw-r--r--nixos/tests/gemstash.nix51
-rw-r--r--nixos/tests/gitlab.nix12
-rw-r--r--nixos/tests/installer.nix6
-rw-r--r--nixos/tests/timescaledb.nix93
27 files changed, 1105 insertions, 93 deletions
diff --git a/nixos/doc/manual/development/replace-modules.section.md b/nixos/doc/manual/development/replace-modules.section.md
index 0c0d6a7ac2f1..ac9f5adbaf98 100644
--- a/nixos/doc/manual/development/replace-modules.section.md
+++ b/nixos/doc/manual/development/replace-modules.section.md
@@ -8,8 +8,15 @@ the system on a stable release.
 
 `disabledModules` is a top level attribute like `imports`, `options` and
 `config`. It contains a list of modules that will be disabled. This can
-either be the full path to the module or a string with the filename
-relative to the modules path (eg. \<nixpkgs/nixos/modules> for nixos).
+either be:
+ - the full path to the module,
+ - or a string with the filename relative to the modules path (eg. \<nixpkgs/nixos/modules> for nixos),
+ - or an attribute set containing a specific `key` attribute.
+
+The latter allows some modules to be disabled, despite them being distributed
+via attributes instead of file paths. The `key` should be globally unique, so
+it is recommended to include a file path in it, or rely on a framework to do it
+for you.
 
 This example will replace the existing postgresql module with the
 version defined in the nixos-unstable channel while keeping the rest of
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index 3e63ddced611..1bd8906cf64f 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -34,6 +34,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
 
+- [gemstash](https://github.com/rubygems/gemstash), a RubyGems.org cache and private gem server. Available as [services.gemstash](#opt-services.gemstash.enable).
+
 - [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer.  Available as [services.gmediarender](options.html#opt-services.gmediarender.enable).
 
 - [stevenblack-blocklist](https://github.com/StevenBlack/hosts), A unified hosts file with base extensions for blocking unwanted websites. Available as [networking.stevenblack](options.html#opt-networking.stevenblack.enable).
@@ -58,6 +60,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [ulogd](https://www.netfilter.org/projects/ulogd/index.html), a userspace logging daemon for netfilter/iptables related logging. Available as [services.ulogd](options.html#opt-services.ulogd.enable).
 
+- [jellyseerr](https://github.com/Fallenbagel/jellyseerr), a web-based requests manager for Jellyfin, forked from Overseerr. Available as [services.jellyseerr](#opt-services.jellyseerr.enable).
+
 - [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable).
 
 - [autosuspend](https://github.com/languitar/autosuspend), a python daemon that suspends a system if certain conditions are met, or not met.
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 763c2038f6d4..3ebe2fa9f164 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -61,7 +61,7 @@ with lib;
       pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
       qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
       qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; });
-      qt5 = super.qt5.overrideScope' (const (super': {
+      qt5 = super.qt5.overrideScope (const (super': {
         qtbase = super'.qtbase.override { withGtk3 = false; };
       }));
       stoken = super.stoken.override { withGTK3 = false; };
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 2b046d19d758..e1e30c99a447 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -440,6 +440,7 @@
   ./services/development/blackfire.nix
   ./services/development/bloop.nix
   ./services/development/distccd.nix
+  ./services/development/gemstash.nix
   ./services/development/hoogle.nix
   ./services/development/jupyter/default.nix
   ./services/development/jupyterhub/default.nix
@@ -624,6 +625,7 @@
   ./services/misc/irkerd.nix
   ./services/misc/jackett.nix
   ./services/misc/jellyfin.nix
+  ./services/misc/jellyseerr.nix
   ./services/misc/klipper.nix
   ./services/misc/languagetool.nix
   ./services/misc/leaps.nix
@@ -1129,6 +1131,7 @@
   ./services/web-apps/baget.nix
   ./services/web-apps/bookstack.nix
   ./services/web-apps/calibre-web.nix
+  ./services/web-apps/coder.nix
   ./services/web-apps/changedetection-io.nix
   ./services/web-apps/cloudlog.nix
   ./services/web-apps/code-server.nix
diff --git a/nixos/modules/programs/java.nix b/nixos/modules/programs/java.nix
index 4f03c1f3ff25..c5f83858d06a 100644
--- a/nixos/modules/programs/java.nix
+++ b/nixos/modules/programs/java.nix
@@ -8,7 +8,6 @@ with lib;
 let
   cfg = config.programs.java;
 in
-
 {
 
   options = {
@@ -40,12 +39,35 @@ in
         type = types.package;
       };
 
+      binfmt = mkEnableOption (lib.mdDoc "binfmt to execute java jar's and classes");
+
     };
 
   };
 
   config = mkIf cfg.enable {
 
+    boot.binfmt.registrations = mkIf cfg.binfmt {
+      java-class = {
+        recognitionType = "extension";
+        magicOrExtension = "class";
+        interpreter = pkgs.writeShellScript "java-class-wrapper" ''
+          test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook
+          classpath=$(dirname "$1")
+          class=$(basename "''${1%%.class}")
+          $JAVA_HOME/bin/java -classpath "$classpath" "$class" "''${@:2}"
+        '';
+      };
+      java-jar = {
+        recognitionType = "extension";
+        magicOrExtension = "jar";
+        interpreter = pkgs.writeShellScript "java-jar-wrapper" ''
+          test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook
+          $JAVA_HOME/bin/java -jar "$@"
+        '';
+      };
+    };
+
     environment.systemPackages = [ cfg.package ];
 
     environment.shellInit = ''
diff --git a/nixos/modules/programs/waybar.nix b/nixos/modules/programs/waybar.nix
index 4697d0f7a622..2c49ae140813 100644
--- a/nixos/modules/programs/waybar.nix
+++ b/nixos/modules/programs/waybar.nix
@@ -2,17 +2,22 @@
 
 with lib;
 
+let
+  cfg = config.programs.waybar;
+in
 {
   options.programs.waybar = {
     enable = mkEnableOption (lib.mdDoc "waybar");
+    package = mkPackageOptionMD pkgs "waybar" { };
   };
 
-  config = mkIf config.programs.waybar.enable {
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
     systemd.user.services.waybar = {
       description = "Waybar as systemd service";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
-      script = "${pkgs.waybar}/bin/waybar";
+      script = "${cfg.package}/bin/waybar";
     };
   };
 
diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index b6eb68cc43f1..b838c174553d 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -47,7 +47,12 @@ let
     then [ "${name} ${value}" ]
     else concatLists (mapAttrsToList (genSection name) value);
 
-  addDefaults = settings: { backend = "btrfs-progs-sudo"; } // settings;
+  sudo_doas =
+    if config.security.sudo.enable then "sudo"
+    else if config.security.doas.enable then "doas"
+    else throw "The btrbk nixos module needs either sudo or doas enabled in the configuration";
+
+  addDefaults = settings: { backend = "btrfs-progs-${sudo_doas}"; } // settings;
 
   mkConfigFile = name: settings: pkgs.writeTextFile {
     name = "btrbk-${name}.conf";
@@ -152,20 +157,41 @@ in
   };
   config = mkIf (sshEnabled || serviceEnabled) {
     environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages;
-    security.sudo.extraRules = [
-      {
-        users = [ "btrbk" ];
-        commands = [
-          { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; }
-          { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
-          { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
-          # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
-          { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; }
-          { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
-          { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
+    security.sudo = mkIf (sudo_doas == "sudo") {
+      extraRules = [
+        {
+            users = [ "btrbk" ];
+            commands = [
+            { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; }
+            { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
+            { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
+            # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
+            { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; }
+            { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
+            { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
+            ];
+        }
+      ];
+    };
+    security.doas = mkIf (sudo_doas == "doas") {
+      extraRules = let
+        doasCmdNoPass = cmd: { users = [ "btrbk" ]; cmd = cmd; noPass = true; };
+      in
+        [
+            (doasCmdNoPass "${pkgs.btrfs-progs}/bin/btrfs")
+            (doasCmdNoPass "${pkgs.coreutils}/bin/mkdir")
+            (doasCmdNoPass "${pkgs.coreutils}/bin/readlink")
+            # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
+            (doasCmdNoPass "/run/current-system/bin/btrfs")
+            (doasCmdNoPass "/run/current-system/sw/bin/mkdir")
+            (doasCmdNoPass "/run/current-system/sw/bin/readlink")
+
+            # doas matches command, not binary
+            (doasCmdNoPass "btrfs")
+            (doasCmdNoPass "mkdir")
+            (doasCmdNoPass "readlink")
         ];
-      }
-    ];
+    };
     users.users.btrbk = {
       isSystemUser = true;
       # ssh needs a home directory
@@ -183,8 +209,9 @@ in
               "best-effort" = 2;
               "realtime" = 1;
             }.${cfg.ioSchedulingClass};
+            sudo_doas_flag = "--${sudo_doas}";
           in
-          ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh --sudo ${options}" ${v.key}''
+          ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh ${sudo_doas_flag} ${options}" ${v.key}''
         )
         cfg.sshAccess;
     };
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 564bcd37dec5..83078706fcae 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -398,7 +398,7 @@ in
     systemd.services.hydra-evaluator =
       { wantedBy = [ "multi-user.target" ];
         requires = [ "hydra-init.service" ];
-        after = [ "hydra-init.service" "network.target" ];
+        after = [ "hydra-init.service" "network.target" "network-online.target" ];
         path = with pkgs; [ hydra-package nettools jq ];
         restartTriggers = [ hydraConf ];
         environment = env // {
diff --git a/nixos/modules/services/development/gemstash.nix b/nixos/modules/services/development/gemstash.nix
new file mode 100644
index 000000000000..eb7ccb98bde8
--- /dev/null
+++ b/nixos/modules/services/development/gemstash.nix
@@ -0,0 +1,103 @@
+{ lib, pkgs, config, ... }:
+with lib;
+
+let
+  settingsFormat = pkgs.formats.yaml { };
+
+  # gemstash uses a yaml config where the keys are ruby symbols,
+  # which means they start with ':'. This would be annoying to use
+  # on the nix side, so we rewrite plain names instead.
+  prefixColon = s: listToAttrs (map
+    (attrName: {
+      name = ":${attrName}";
+      value =
+        if isAttrs s.${attrName}
+        then prefixColon s."${attrName}"
+        else s."${attrName}";
+    })
+    (attrNames s));
+
+  # parse the port number out of the tcp://ip:port bind setting string
+  parseBindPort = bind: strings.toInt (last (strings.splitString ":" bind));
+
+  cfg = config.services.gemstash;
+in
+{
+  options.services.gemstash = {
+    enable = mkEnableOption (lib.mdDoc "gemstash service");
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to open the firewall for the port in {option}`services.gemstash.bind`.
+      '';
+    };
+
+    settings = mkOption {
+      default = {};
+      description = lib.mdDoc ''
+        Configuration for Gemstash. The details can be found at in
+        [gemstash documentation](https://github.com/rubygems/gemstash/blob/master/man/gemstash-configuration.5.md).
+        Each key set here is automatically prefixed with ":" to match the gemstash expectations.
+      '';
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          base_path = mkOption {
+            type = types.path;
+            default = "/var/lib/gemstash";
+            description = lib.mdDoc "Path to store the gem files and the sqlite database. If left unchanged, the directory will be created.";
+          };
+          bind = mkOption {
+            type = types.str;
+            default = "tcp://0.0.0.0:9292";
+            description = lib.mdDoc "Host and port combination for the server to listen on.";
+          };
+          db_adapter = mkOption {
+            type = types.nullOr (types.enum [ "sqlite3" "postgres" "mysql" "mysql2" ]);
+            default = null;
+            description = lib.mdDoc "Which database type to use. For choices other than sqlite3, the dbUrl has to be specified as well.";
+          };
+          db_url = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = lib.mdDoc "The database to connect to when using postgres, mysql, or mysql2.";
+          };
+        };
+      };
+    };
+  };
+
+  config =
+    mkIf cfg.enable {
+      users = {
+        users.gemstash = {
+          group = "gemstash";
+          isSystemUser = true;
+        };
+        groups.gemstash = { };
+      };
+
+      networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ (parseBindPort cfg.settings.bind) ];
+
+      systemd.services.gemstash = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig = mkMerge [
+          {
+            ExecStart = "${pkgs.gemstash}/bin/gemstash start --no-daemonize --config-file ${settingsFormat.generate "gemstash.yaml" (prefixColon cfg.settings)}";
+            NoNewPrivileges = true;
+            User = "gemstash";
+            Group = "gemstash";
+            PrivateTmp = true;
+            RestrictSUIDSGID = true;
+            LockPersonality = true;
+          }
+          (mkIf (cfg.settings.base_path == "/var/lib/gemstash") {
+            StateDirectory = "gemstash";
+          })
+        ];
+      };
+    };
+}
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 179359c97a3a..c7299c1ccad8 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -89,11 +89,6 @@ let
     };
   };
 
-  pagesArgs = [
-    "-pages-domain" gitlabConfig.production.pages.host
-    "-pages-root" "${gitlabConfig.production.shared.path}/pages"
-  ] ++ cfg.pagesExtraArgs;
-
   gitlabConfig = {
     # These are the default settings from config/gitlab.example.yml
     production = flip recursiveUpdate cfg.extraConfig {
@@ -161,6 +156,12 @@ let
       };
       extra = {};
       uploads.storage_path = cfg.statePath;
+      pages = {
+        enabled = cfg.pages.enable;
+        port = 8090;
+        host = cfg.pages.settings.pages-domain;
+        secret_file = cfg.pages.settings.api-secret-key;
+      };
     };
   };
 
@@ -246,6 +247,7 @@ in {
     (mkRenamedOptionModule [ "services" "gitlab" "backupPath" ] [ "services" "gitlab" "backup" "path" ])
     (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
     (mkRemovedOptionModule [ "services" "gitlab" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.gitlab directly instead")
+    (mkRemovedOptionModule [ "services" "gitlab" "pagesExtraArgs" ] "Use services.gitlab.pages.settings instead")
   ];
 
   options = {
@@ -667,10 +669,127 @@ in {
         };
       };
 
-      pagesExtraArgs = mkOption {
-        type = types.listOf types.str;
-        default = [ "-listen-proxy" "127.0.0.1:8090" ];
-        description = lib.mdDoc "Arguments to pass to the gitlab-pages daemon";
+      pages.enable = mkEnableOption (lib.mdDoc "the GitLab Pages service");
+
+      pages.settings = mkOption {
+        example = literalExpression ''
+          {
+            pages-domain = "example.com";
+            auth-client-id = "generated-id-xxxxxxx";
+            auth-client-secret = { _secret = "/var/keys/auth-client-secret"; };
+            auth-redirect-uri = "https://projects.example.com/auth";
+            auth-secret = { _secret = "/var/keys/auth-secret"; };
+            auth-server = "https://gitlab.example.com";
+          }
+        '';
+
+        description = lib.mdDoc ''
+          Configuration options to set in the GitLab Pages config
+          file.
+
+          Options containing secret data should be set to an attribute
+          set containing the attribute `_secret` - a string pointing
+          to a file containing the value the option should be set
+          to. See the example to get a better picture of this: in the
+          resulting configuration file, the `auth-client-secret` and
+          `auth-secret` keys will be set to the contents of the
+          {file}`/var/keys/auth-client-secret` and
+          {file}`/var/keys/auth-secret` files respectively.
+        '';
+
+        type = types.submodule {
+          freeformType = with types; attrsOf (nullOr (oneOf [ str int bool attrs ]));
+
+          options = {
+            listen-http = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [];
+              description = lib.mdDoc ''
+                The address(es) to listen on for HTTP requests.
+              '';
+            };
+
+            listen-https = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [];
+              description = lib.mdDoc ''
+                The address(es) to listen on for HTTPS requests.
+              '';
+            };
+
+            listen-proxy = mkOption {
+              type = with types; listOf str;
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
+              default = [ "127.0.0.1:8090" ];
+              description = lib.mdDoc ''
+                The address(es) to listen on for proxy requests.
+              '';
+            };
+
+            artifacts-server = mkOption {
+              type = with types; nullOr str;
+              default = "http${optionalString cfg.https "s"}://${cfg.host}/api/v4";
+              defaultText = "http(s)://<services.gitlab.host>/api/v4";
+              example = "https://gitlab.example.com/api/v4";
+              description = lib.mdDoc ''
+                API URL to proxy artifact requests to.
+              '';
+            };
+
+            gitlab-server = mkOption {
+              type = with types; nullOr str;
+              default = "http${optionalString cfg.https "s"}://${cfg.host}";
+              defaultText = "http(s)://<services.gitlab.host>";
+              example = "https://gitlab.example.com";
+              description = lib.mdDoc ''
+                Public GitLab server URL.
+              '';
+            };
+
+            internal-gitlab-server = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              defaultText = "http(s)://<services.gitlab.host>";
+              example = "https://gitlab.example.internal";
+              description = lib.mdDoc ''
+                Internal GitLab server used for API requests, useful
+                if you want to send that traffic over an internal load
+                balancer. By default, the value of
+                `services.gitlab.pages.settings.gitlab-server` is
+                used.
+              '';
+            };
+
+            api-secret-key = mkOption {
+              type = with types; nullOr str;
+              default = "${cfg.statePath}/gitlab_pages_secret";
+              internal = true;
+              description = lib.mdDoc ''
+                File with secret key used to authenticate with the
+                GitLab API.
+              '';
+            };
+
+            pages-domain = mkOption {
+              type = with types; nullOr str;
+              example = "example.com";
+              description = lib.mdDoc ''
+                The domain to serve static pages on.
+              '';
+            };
+
+            pages-root = mkOption {
+              type = types.str;
+              default = "${gitlabConfig.production.shared.path}/pages";
+              defaultText = literalExpression ''config.${opt.extraConfig}.production.shared.path + "/pages"'';
+              description = lib.mdDoc ''
+                The directory where pages are stored.
+              '';
+            };
+          };
+        };
       };
 
       secrets.secretFile = mkOption {
@@ -1210,6 +1329,9 @@ in {
             umask u=rwx,g=,o=
 
             openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+            ${optionalString cfg.pages.enable ''
+                openssl rand -base64 32 > ${cfg.pages.settings.api-secret-key}
+            ''}
 
             rm -f '${cfg.statePath}/config/database.yml'
 
@@ -1359,28 +1481,66 @@ in {
       };
     };
 
-    systemd.services.gitlab-pages = mkIf (gitlabConfig.production.pages.enabled or false) {
-      description = "GitLab static pages daemon";
-      after = [ "network.target" "gitlab-config.service" ];
-      bindsTo = [ "gitlab-config.service" ];
-      wantedBy = [ "gitlab.target" ];
-      partOf = [ "gitlab.target" ];
-
-      path = [ pkgs.unzip ];
-
-      serviceConfig = {
-        Type = "simple";
-        TimeoutSec = "infinity";
-        Restart = "on-failure";
-
-        User = cfg.user;
-        Group = cfg.group;
-
-        ExecStart = "${cfg.packages.pages}/bin/gitlab-pages ${escapeShellArgs pagesArgs}";
-        WorkingDirectory = gitlabEnv.HOME;
-      };
+    services.gitlab.pages.settings = {
+      api-secret-key = "${cfg.statePath}/gitlab_pages_secret";
     };
 
+    systemd.services.gitlab-pages =
+      let
+        filteredConfig = filterAttrs (_: v: v != null) cfg.pages.settings;
+        isSecret = v: isAttrs v && v ? _secret && isString v._secret;
+        mkPagesKeyValue = lib.generators.toKeyValue {
+          mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" rec {
+            mkValueString = v:
+              if isInt           v then toString v
+              else if isString   v then v
+              else if true  ==   v then "true"
+              else if false ==   v then "false"
+              else if isSecret   v then builtins.hashString "sha256" v._secret
+              else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
+          };
+        };
+        secretPaths = lib.catAttrs "_secret" (lib.collect isSecret filteredConfig);
+        mkSecretReplacement = file: ''
+          replace-secret ${lib.escapeShellArgs [ (builtins.hashString "sha256" file) file "/run/gitlab-pages/gitlab-pages.conf" ]}
+        '';
+        secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
+        configFile = pkgs.writeText "gitlab-pages.conf" (mkPagesKeyValue filteredConfig);
+      in
+        mkIf cfg.pages.enable {
+          description = "GitLab static pages daemon";
+          after = [ "network.target" "gitlab-config.service" "gitlab.service" ];
+          bindsTo = [ "gitlab-config.service" "gitlab.service" ];
+          wantedBy = [ "gitlab.target" ];
+          partOf = [ "gitlab.target" ];
+
+          path = with pkgs; [
+            unzip
+            replace-secret
+          ];
+
+          serviceConfig = {
+            Type = "simple";
+            TimeoutSec = "infinity";
+            Restart = "on-failure";
+
+            User = cfg.user;
+            Group = cfg.group;
+
+            ExecStartPre = pkgs.writeShellScript "gitlab-pages-pre-start" ''
+              set -o errexit -o pipefail -o nounset
+              shopt -s dotglob nullglob inherit_errexit
+
+              install -m u=rw ${configFile} /run/gitlab-pages/gitlab-pages.conf
+              ${secretReplacements}
+            '';
+            ExecStart = "${cfg.packages.pages}/bin/gitlab-pages -config=/run/gitlab-pages/gitlab-pages.conf";
+            WorkingDirectory = gitlabEnv.HOME;
+            RuntimeDirectory = "gitlab-pages";
+            RuntimeDirectoryMode = "0700";
+          };
+        };
+
     systemd.services.gitlab-workhorse = {
       after = [ "network.target" ];
       wantedBy = [ "gitlab.target" ];
diff --git a/nixos/modules/services/misc/jellyseerr.nix b/nixos/modules/services/misc/jellyseerr.nix
new file mode 100644
index 000000000000..31e0c5beb673
--- /dev/null
+++ b/nixos/modules/services/misc/jellyseerr.nix
@@ -0,0 +1,62 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.jellyseerr;
+in
+{
+  meta.maintainers = [ maintainers.camillemndn ];
+
+  options.services.jellyseerr = {
+    enable = mkEnableOption (mdDoc ''Jellyseerr, a requests manager for Jellyfin'');
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''Open port in the firewall for the Jellyseerr web interface.'';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 5055;
+      description = mdDoc ''The port which the Jellyseerr web UI should listen to.'';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.jellyseerr = {
+      description = "Jellyseerr, a requests manager for Jellyfin";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.PORT = toString cfg.port;
+      serviceConfig = {
+        Type = "exec";
+        StateDirectory = "jellyseerr";
+        WorkingDirectory = "${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr";
+        DynamicUser = true;
+        ExecStart = "${pkgs.jellyseerr}/bin/jellyseerr";
+        BindPaths = [ "/var/lib/jellyseerr/:${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr/config/" ];
+        Restart = "on-failure";
+        ProtectHome = true;
+        ProtectSystem = "strict";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        NoNewPrivileges = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+        PrivateMounts = true;
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index f516b75ab10f..4f197b9b5820 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -1408,7 +1408,7 @@ let
       '';
 
       action =
-        mkDefOpt (types.enum [ "replace" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" ''
+        mkDefOpt (types.enum [ "replace" "lowercase" "uppercase" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" ''
           Action to perform based on regex matching.
         '';
     };
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index 32498ca25ea8..9ec4f57ca43e 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -42,6 +42,8 @@ let
     ${if cfg.sslKey  == "" then "" else "sslKey="+cfg.sslKey}
     ${if cfg.sslCa   == "" then "" else "sslCA="+cfg.sslCa}
 
+    ${lib.optionalString (cfg.dbus != null) "dbus=${cfg.dbus}"}
+
     ${cfg.extraConfig}
   '';
 in
@@ -282,6 +284,12 @@ in
           `murmur` is running.
         '';
       };
+
+      dbus = mkOption {
+        type = types.enum [ null "session" "system" ];
+        default = null;
+        description = lib.mdDoc "Enable D-Bus remote control. Set to the bus you want Murmur to connect to.";
+      };
     };
   };
 
@@ -325,5 +333,27 @@ in
         Group = "murmur";
       };
     };
+
+    # currently not included in upstream package, addition requested at
+    # https://github.com/mumble-voip/mumble/issues/6078
+    services.dbus.packages = mkIf (cfg.dbus == "system") [(pkgs.writeTextFile {
+      name = "murmur-dbus-policy";
+      text = ''
+        <!DOCTYPE busconfig PUBLIC
+          "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+          "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+        <busconfig>
+          <policy user="murmur">
+            <allow own="net.sourceforge.mumble.murmur"/>
+          </policy>
+
+          <policy context="default">
+            <allow send_destination="net.sourceforge.mumble.murmur"/>
+            <allow receive_sender="net.sourceforge.mumble.murmur"/>
+          </policy>
+        </busconfig>
+      '';
+      destination = "/share/dbus-1/system.d/murmur.conf";
+    })];
   };
 }
diff --git a/nixos/modules/services/networking/networkd-dispatcher.nix b/nixos/modules/services/networking/networkd-dispatcher.nix
index d13ca23368c5..c5319ca7b88a 100644
--- a/nixos/modules/services/networking/networkd-dispatcher.nix
+++ b/nixos/modules/services/networking/networkd-dispatcher.nix
@@ -3,8 +3,11 @@
 with lib;
 
 let
+
   cfg = config.services.networkd-dispatcher;
+
 in {
+
   options = {
     services.networkd-dispatcher = {
 
@@ -14,14 +17,49 @@ in {
         for usage.
       '');
 
-      scriptDir = mkOption {
-        type = types.path;
-        default = "/var/lib/networkd-dispatcher";
-        description = mdDoc ''
-          This directory is used for keeping various scripts read and run by
-          networkd-dispatcher. See [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions)
-          for directory structure and script usage.
+      rules = mkOption {
+        default = {};
+        example = lib.literalExpression ''
+          { "restart-tor" = {
+              onState = ["routable" "off"];
+              script = '''
+                #!''${pkgs.runtimeShell}
+                if [[ $IFACE == "wlan0" && $AdministrativeState == "configured" ]]; then
+                  echo "Restarting Tor ..."
+                  systemctl restart tor
+                fi
+                exit 0
+              ''';
+            };
+          };
+        '';
+        description = lib.mdDoc ''
+          Declarative configuration of networkd-dispatcher rules. See
+          [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions)
+          for an introduction and example scripts.
         '';
+        type = types.attrsOf (types.submodule {
+          options = {
+            onState = mkOption {
+              type = types.listOf (types.enum [
+                "routable" "dormant" "no-carrier" "off" "carrier" "degraded"
+                "configuring" "configured"
+              ]);
+              default = null;
+              description = lib.mdDoc ''
+                List of names of the systemd-networkd operational states which
+                should trigger the script. See <https://www.freedesktop.org/software/systemd/man/networkctl.html>
+                for a description of the specific state type.
+              '';
+            };
+            script = mkOption {
+              type = types.lines;
+              description = lib.mdDoc ''
+                Shell commands executed on specified operational states.
+              '';
+            };
+          };
+        });
       };
 
     };
@@ -30,34 +68,31 @@ in {
   config = mkIf cfg.enable {
 
     systemd = {
-
       packages = [ pkgs.networkd-dispatcher ];
       services.networkd-dispatcher = {
         wantedBy = [ "multi-user.target" ];
         # Override existing ExecStart definition
-        serviceConfig.ExecStart = [
+        serviceConfig.ExecStart = let
+          scriptDir = pkgs.symlinkJoin {
+            name = "networkd-dispatcher-script-dir";
+            paths = lib.mapAttrsToList (name: cfg:
+              (map(state:
+                pkgs.writeTextFile {
+                  inherit name;
+                  text = cfg.script;
+                  destination = "/${state}.d/${name}";
+                  executable = true;
+                }
+              ) cfg.onState)
+            ) cfg.rules;
+          };
+        in [
           ""
-          "${pkgs.networkd-dispatcher}/bin/networkd-dispatcher -v --script-dir ${cfg.scriptDir} $networkd_dispatcher_args"
+          "${pkgs.networkd-dispatcher}/bin/networkd-dispatcher -v --script-dir ${scriptDir} $networkd_dispatcher_args"
         ];
       };
-
-      # Directory structure required according to upstream instructions
-      # https://gitlab.com/craftyguy/networkd-dispatcher
-      tmpfiles.rules = [
-        "d '${cfg.scriptDir}'               0750 root root - -"
-        "d '${cfg.scriptDir}/routable.d'    0750 root root - -"
-        "d '${cfg.scriptDir}/dormant.d'     0750 root root - -"
-        "d '${cfg.scriptDir}/no-carrier.d'  0750 root root - -"
-        "d '${cfg.scriptDir}/off.d'         0750 root root - -"
-        "d '${cfg.scriptDir}/carrier.d'     0750 root root - -"
-        "d '${cfg.scriptDir}/degraded.d'    0750 root root - -"
-        "d '${cfg.scriptDir}/configuring.d' 0750 root root - -"
-        "d '${cfg.scriptDir}/configured.d'  0750 root root - -"
-      ];
-
     };
 
-
   };
 }
 
diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix
new file mode 100644
index 000000000000..469a29bc3aa8
--- /dev/null
+++ b/nixos/modules/services/web-apps/coder.nix
@@ -0,0 +1,217 @@
+{ config, lib, options, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.coder;
+  name = "coder";
+in {
+  options = {
+    services.coder = {
+      enable = mkEnableOption (lib.mdDoc "Coder service");
+
+      user = mkOption {
+        type = types.str;
+        default = "coder";
+        description = lib.mdDoc ''
+          User under which the coder service runs.
+
+          ::: {.note}
+          If left as the default value this user will automatically be created
+          on system activation, otherwise it needs to be configured manually.
+          :::
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "coder";
+        description = lib.mdDoc ''
+          Group under which the coder service runs.
+
+          ::: {.note}
+          If left as the default value this group will automatically be created
+          on system activation, otherwise it needs to be configured manually.
+          :::
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.coder;
+        description = lib.mdDoc ''
+          Package to use for the service.
+        '';
+        defaultText = literalExpression "pkgs.coder";
+      };
+
+      homeDir = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Home directory for coder user.
+        '';
+        default = "/var/lib/coder";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Listen address.
+        '';
+        default = "127.0.0.1:3000";
+      };
+
+      accessUrl = mkOption {
+        type = types.nullOr types.str;
+        description = lib.mdDoc ''
+          Access URL should be a external IP address or domain with DNS records pointing to Coder.
+        '';
+        default = null;
+        example = "https://coder.example.com";
+      };
+
+      wildcardAccessUrl = mkOption {
+        type = types.nullOr types.str;
+        description = lib.mdDoc ''
+          If you are providing TLS certificates directly to the Coder server, you must use a single certificate for the root and wildcard domains.
+        '';
+        default = null;
+        example = "*.coder.example.com";
+      };
+
+      database = {
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Create the database and database user locally.
+          '';
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "/run/postgresql";
+          description = lib.mdDoc ''
+            Hostname hosting the database.
+          '';
+        };
+
+        database = mkOption {
+          type = types.str;
+          default = "coder";
+          description = lib.mdDoc ''
+            Name of database.
+          '';
+        };
+
+        username = mkOption {
+          type = types.str;
+          default = "coder";
+          description = lib.mdDoc ''
+            Username for accessing the database.
+          '';
+        };
+
+        password = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc ''
+            Password for accessing the database.
+          '';
+        };
+
+        sslmode = mkOption {
+          type = types.nullOr types.str;
+          default = "disable";
+          description = lib.mdDoc ''
+            Password for accessing the database.
+          '';
+        };
+      };
+
+      tlsCert = mkOption {
+        type = types.nullOr types.path;
+        description = lib.mdDoc ''
+          The path to the TLS certificate.
+        '';
+        default = null;
+      };
+
+      tlsKey = mkOption {
+        type = types.nullOr types.path;
+        description = lib.mdDoc ''
+          The path to the TLS key.
+        '';
+        default = null;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.username == name;
+        message = "services.coder.database.username must be set to ${user} if services.coder.database.createLocally is set true";
+      }
+    ];
+
+    systemd.services.coder = {
+      description = "Coder - Self-hosted developer workspaces on your infra";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        CODER_ACCESS_URL = cfg.accessUrl;
+        CODER_WILDCARD_ACCESS_URL = cfg.wildcardAccessUrl;
+        CODER_PG_CONNECTION_URL = "user=${cfg.database.username} ${optionalString (cfg.database.password != null) "password=${cfg.database.password}"} database=${cfg.database.database} host=${cfg.database.host} ${optionalString (cfg.database.sslmode != null) "sslmode=${cfg.database.sslmode}"}";
+        CODER_ADDRESS = cfg.listenAddress;
+        CODER_TLS_ENABLE = optionalString (cfg.tlsCert != null) "1";
+        CODER_TLS_CERT_FILE = cfg.tlsCert;
+        CODER_TLS_KEY_FILE = cfg.tlsKey;
+      };
+
+      serviceConfig = {
+        ProtectSystem = "full";
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+        SecureBits = "keep-caps";
+        AmbientCapabilities = "CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
+        CacheDirectory = "coder";
+        CapabilityBoundingSet = "CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE";
+        KillSignal = "SIGINT";
+        KillMode = "mixed";
+        NoNewPrivileges = "yes";
+        Restart = "on-failure";
+        ExecStart = "${cfg.package}/bin/coder server";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+    services.postgresql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [
+        cfg.database.database
+      ];
+      ensureUsers = [{
+        name = cfg.database.username;
+        ensurePermissions = {
+          "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES";
+        };
+        }
+      ];
+    };
+
+    users.groups = optionalAttrs (cfg.group == name) {
+      "${cfg.group}" = {};
+    };
+    users.users = optionalAttrs (cfg.user == name) {
+      ${name} = {
+        description = "Coder service user";
+        group = cfg.group;
+        home = cfg.homeDir;
+        createHome = true;
+        isSystemUser = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/x11/desktop-managers/phosh.nix b/nixos/modules/services/x11/desktop-managers/phosh.nix
index e889c0e34e7d..3cfa6e044b73 100644
--- a/nixos/modules/services/x11/desktop-managers/phosh.nix
+++ b/nixos/modules/services/x11/desktop-managers/phosh.nix
@@ -173,7 +173,7 @@ in
     systemd.services.phosh = {
       wantedBy = [ "graphical.target" ];
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/phosh";
+        ExecStart = "${cfg.package}/bin/phosh-session";
         User = cfg.user;
         Group = cfg.group;
         PAMName = "login";
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 73322696aeac..f0c4b2172f9d 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -448,6 +448,7 @@ in
             kio-extras
           ];
           optionalPackages = [
+            ark
             elisa
             gwenview
             okular
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 679a663362b6..8b20f9a7e87f 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -614,7 +614,7 @@ in
 
     # Avoid potentially degraded system state due to
     # "Userspace Out-Of-Memory (OOM) Killer was skipped because of a failed condition check (ConditionControlGroupController=v2)."
-    systemd.services.systemd-oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false;
+    systemd.oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false;
 
     services.logrotate.settings = {
       "/var/log/btmp" = mapAttrs (_: mkDefault) {
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index c98916f60fa3..f7e82963fbad 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -895,7 +895,7 @@ in
 
         ${optionalString cfg.writableStore ''
           echo "mounting overlay filesystem on /nix/store..."
-          mkdir -p 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store
+          mkdir -p -m 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store
           mount -t overlay overlay $targetRoot/nix/store \
             -o lowerdir=$targetRoot/nix/.ro-store,upperdir=$targetRoot/nix/.rw-store/store,workdir=$targetRoot/nix/.rw-store/work || fail
         ''}
@@ -1097,7 +1097,7 @@ in
         unitConfig.DefaultDependencies = false;
         serviceConfig = {
           Type = "oneshot";
-          ExecStart = "/bin/mkdir -p 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
+          ExecStart = "/bin/mkdir -p -m 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
         };
       };
     };
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 07adf3dbeba5..0c3310cabe42 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -108,6 +108,7 @@ in {
   breitbandmessung = handleTest ./breitbandmessung.nix {};
   brscan5 = handleTest ./brscan5.nix {};
   btrbk = handleTest ./btrbk.nix {};
+  btrbk-doas = handleTest ./btrbk-doas.nix {};
   btrbk-no-timer = handleTest ./btrbk-no-timer.nix {};
   btrbk-section-order = handleTest ./btrbk-section-order.nix {};
   buildbot = handleTest ./buildbot.nix {};
@@ -137,6 +138,7 @@ in {
   cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {};
   cockpit = handleTest ./cockpit.nix {};
   cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {};
+  coder = handleTest ./coder.nix {};
   collectd = handleTest ./collectd.nix {};
   connman = handleTest ./connman.nix {};
   consul = handleTest ./consul.nix {};
@@ -238,6 +240,7 @@ in {
   ft2-clone = handleTest ./ft2-clone.nix {};
   mimir = handleTest ./mimir.nix {};
   garage = handleTest ./garage {};
+  gemstash = handleTest ./gemstash.nix {};
   gerrit = handleTest ./gerrit.nix {};
   geth = handleTest ./geth.nix {};
   ghostunnel = handleTest ./ghostunnel.nix {};
@@ -687,6 +690,7 @@ in {
   terminal-emulators = handleTest ./terminal-emulators.nix {};
   tiddlywiki = handleTest ./tiddlywiki.nix {};
   tigervnc = handleTest ./tigervnc.nix {};
+  timescaledb = handleTest ./timescaledb.nix {};
   timezone = handleTest ./timezone.nix {};
   tinc = handleTest ./tinc {};
   tinydns = handleTest ./tinydns.nix {};
diff --git a/nixos/tests/btrbk-doas.nix b/nixos/tests/btrbk-doas.nix
new file mode 100644
index 000000000000..1e3f8d56addb
--- /dev/null
+++ b/nixos/tests/btrbk-doas.nix
@@ -0,0 +1,114 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+  let
+    privateKey = ''
+      -----BEGIN OPENSSH PRIVATE KEY-----
+      b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+      QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
+      RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
+      AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
+      9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
+      -----END OPENSSH PRIVATE KEY-----
+    '';
+    publicKey = ''
+      ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv
+    '';
+  in
+  {
+    name = "btrbk-doas";
+    meta = with pkgs.lib; {
+      maintainers = with maintainers; [ symphorien tu-maurice ];
+    };
+
+    nodes = {
+      archive = { ... }: {
+        security.sudo.enable = false;
+        security.doas.enable = true;
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        # note: this makes the privateKey world readable.
+        # don't do it with real ssh keys.
+        environment.etc."btrbk_key".text = privateKey;
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          instances = {
+            remote = {
+              onCalendar = "minutely";
+              settings = {
+                ssh_identity = "/etc/btrbk_key";
+                ssh_user = "btrbk";
+                stream_compress = "lz4";
+                volume = {
+                  "ssh://main/mnt" = {
+                    target = "/mnt";
+                    snapshot_dir = "btrbk/remote";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+
+      main = { ... }: {
+        security.sudo.enable = false;
+        security.doas.enable = true;
+        environment.systemPackages = with pkgs; [ btrfs-progs ];
+        services.openssh = {
+          enable = true;
+          passwordAuthentication = false;
+          kbdInteractiveAuthentication = false;
+        };
+        services.btrbk = {
+          extraPackages = [ pkgs.lz4 ];
+          sshAccess = [
+            {
+              key = publicKey;
+              roles = [ "source" "send" "info" "delete" ];
+            }
+          ];
+          instances = {
+            local = {
+              onCalendar = "minutely";
+              settings = {
+                volume = {
+                  "/mnt" = {
+                    snapshot_dir = "btrbk/local";
+                    subvolume = "to_backup";
+                  };
+                };
+              };
+            };
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      # create btrfs partition at /mnt
+      for machine in (archive, main):
+        machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1")
+        machine.succeed("mkfs.btrfs /data_fs")
+        machine.succeed("mkdir /mnt")
+        machine.succeed("mount /data_fs /mnt")
+
+      # what to backup and where
+      main.succeed("btrfs subvolume create /mnt/to_backup")
+      main.succeed("mkdir -p /mnt/btrbk/{local,remote}")
+
+      # check that local snapshots work
+      with subtest("local"):
+          main.succeed("echo foo > /mnt/to_backup/bar")
+          main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo")
+          main.succeed("echo bar > /mnt/to_backup/bar")
+          main.succeed("cat /mnt/btrbk/local/*/bar | grep foo")
+
+      # check that btrfs send/receive works and ssh access works
+      with subtest("remote"):
+          archive.wait_until_succeeds("cat /mnt/*/bar | grep bar")
+          main.succeed("echo baz > /mnt/to_backup/bar")
+          archive.succeed("cat /mnt/*/bar | grep bar")
+    '';
+  })
diff --git a/nixos/tests/coder.nix b/nixos/tests/coder.nix
new file mode 100644
index 000000000000..12813827284b
--- /dev/null
+++ b/nixos/tests/coder.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "coder";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ shyim ghuntley ];
+  };
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.coder = {
+        enable = true;
+        accessUrl = "http://localhost:3000";
+      };
+    };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("postgresql.service")
+    machine.wait_for_unit("coder.service")
+    machine.wait_for_open_port(3000)
+
+    machine.succeed("curl --fail http://localhost:3000")
+  '';
+})
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index 98704ecb2fb6..44b583ebcea5 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -1,6 +1,52 @@
 # this test creates a simple GNU image with docker tools and sees if it executes
 
-import ./make-test-python.nix ({ pkgs, ... }: {
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  # nixpkgs#214434: dockerTools.buildImage fails to unpack base images
+  # containing duplicate layers when those duplicate tarballs
+  # appear under the manifest's 'Layers'. Docker can generate images
+  # like this even though dockerTools does not.
+  repeatedLayerTestImage =
+    let
+      # Rootfs diffs for layers 1 and 2 are identical (and empty)
+      layer1 = pkgs.dockerTools.buildImage {  name = "empty";  };
+      layer2 = layer1.overrideAttrs (_: { fromImage = layer1; });
+      repeatedRootfsDiffs = pkgs.runCommandNoCC "image-with-links.tar" {
+        nativeBuildInputs = [pkgs.jq];
+      } ''
+        mkdir contents
+        tar -xf "${layer2}" -C contents
+        cd contents
+        first_rootfs=$(jq -r '.[0].Layers[0]' manifest.json)
+        second_rootfs=$(jq -r '.[0].Layers[1]' manifest.json)
+        target_rootfs=$(sha256sum "$first_rootfs" | cut -d' ' -f 1).tar
+
+        # Replace duplicated rootfs diffs with symlinks to one tarball
+        chmod -R ug+w .
+        mv "$first_rootfs" "$target_rootfs"
+        rm "$second_rootfs"
+        ln -s "../$target_rootfs" "$first_rootfs"
+        ln -s "../$target_rootfs" "$second_rootfs"
+
+        # Update manifest's layers to use the symlinks' target
+        cat manifest.json | \
+        jq ".[0].Layers[0] = \"$target_rootfs\"" |
+        jq ".[0].Layers[1] = \"$target_rootfs\"" > manifest.json.new
+        mv manifest.json.new manifest.json
+
+        tar --sort=name --hard-dereference -cf $out .
+        '';
+    in pkgs.dockerTools.buildImage {
+      fromImage = repeatedRootfsDiffs;
+      name = "repeated-layer-test";
+      tag = "latest";
+      copyToRoot = pkgs.bash;
+      # A runAsRoot script is required to force previous layers to be unpacked
+      runAsRoot = ''
+        echo 'runAsRoot has run.'
+      '';
+    };
+in {
   name = "docker-tools";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ lnl7 roberth ];
@@ -221,6 +267,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             "docker run --rm ${examples.layersUnpackOrder.imageName} cat /layer-order"
         )
 
+    with subtest("Ensure repeated base layers handled by buildImage"):
+        docker.succeed(
+            "docker load --input='${repeatedLayerTestImage}'",
+            "docker run --rm ${repeatedLayerTestImage.imageName} /bin/bash -c 'exit 0'"
+        )
+
     with subtest("Ensure environment variables are correctly inherited"):
         docker.succeed(
             "docker load --input='${examples.environmentVariables}'"
diff --git a/nixos/tests/gemstash.nix b/nixos/tests/gemstash.nix
new file mode 100644
index 000000000000..bc152e42e92e
--- /dev/null
+++ b/nixos/tests/gemstash.nix
@@ -0,0 +1,51 @@
+{ system ? builtins.currentSystem, config ? { }
+, pkgs ? import ../.. { inherit system config; } }:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let common_meta = { maintainers = [ maintainers.viraptor ]; };
+in
+{
+  gemstash_works = makeTest {
+    name = "gemstash-works";
+    meta = common_meta;
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.gemstash = {
+        enable = true;
+      };
+    };
+
+    # gemstash responds to http requests
+    testScript = ''
+      machine.wait_for_unit("gemstash.service")
+      machine.wait_for_file("/var/lib/gemstash")
+      machine.wait_for_open_port(9292)
+      machine.succeed("curl http://localhost:9292")
+    '';
+  };
+
+  gemstash_custom_port = makeTest {
+    name = "gemstash-custom-port";
+    meta = common_meta;
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.gemstash = {
+        enable = true;
+        openFirewall = true;
+        settings = {
+          bind = "tcp://0.0.0.0:12345";
+        };
+      };
+    };
+
+    # gemstash responds to http requests
+    testScript = ''
+      machine.wait_for_unit("gemstash.service")
+      machine.wait_for_file("/var/lib/gemstash")
+      machine.wait_for_open_port(12345)
+      machine.succeed("curl http://localhost:12345")
+    '';
+  };
+}
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index 59dbc99052a6..c2a11bada0a3 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -69,6 +69,10 @@ in {
         databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4";
         initialRootPasswordFile = pkgs.writeText "rootPassword" initialRootPassword;
         smtp.enable = true;
+        pages = {
+          enable = true;
+          settings.pages-domain = "localhost";
+        };
         extraConfig = {
           incoming_email = {
             enabled = true;
@@ -79,11 +83,6 @@ in {
             host = "localhost";
             port = 143;
           };
-          # https://github.com/NixOS/nixpkgs/issues/132295
-          # pages = {
-          #   enabled = true;
-          #   host = "localhost";
-          # };
         };
         secrets = {
           secretFile = pkgs.writeText "secret" "Aig5zaic";
@@ -171,10 +170,9 @@ in {
       waitForServices = ''
         gitlab.wait_for_unit("gitaly.service")
         gitlab.wait_for_unit("gitlab-workhorse.service")
-        # https://github.com/NixOS/nixpkgs/issues/132295
-        # gitlab.wait_for_unit("gitlab-pages.service")
         gitlab.wait_for_unit("gitlab-mailroom.service")
         gitlab.wait_for_unit("gitlab.service")
+        gitlab.wait_for_unit("gitlab-pages.service")
         gitlab.wait_for_unit("gitlab-sidekiq.service")
         gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
         gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 3adfa979edcc..d441765fe194 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -77,9 +77,9 @@ let
     let iface = if grubVersion == 1 then "ide" else "virtio";
         isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
         bios  = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd";
-    in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then
-      throw "Non-EFI boot methods are only supported on i686 / x86_64"
-    else ''
+    in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
+      machine.succeed("true")
+    '' else ''
       def assemble_qemu_flags():
           flags = "-cpu max"
           ${if (system == "x86_64-linux" || system == "i686-linux")
diff --git a/nixos/tests/timescaledb.nix b/nixos/tests/timescaledb.nix
new file mode 100644
index 000000000000..00a7f9af09fb
--- /dev/null
+++ b/nixos/tests/timescaledb.nix
@@ -0,0 +1,93 @@
+# mostly copied from ./postgresql.nix as it seemed unapproriate to
+# test additional extensions for postgresql there.
+
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE EXTENSION timescaledb;
+    CREATE EXTENSION timescaledb_toolkit;
+
+    CREATE TABLE sth (
+      time TIMESTAMPTZ NOT NULL,
+      value DOUBLE PRECISION
+    );
+
+    SELECT create_hypertable('sth', 'time');
+
+    INSERT INTO sth (time, value) VALUES
+    ('2003-04-12 04:05:06 America/New_York', 1.0),
+    ('2003-04-12 04:05:07 America/New_York', 2.0),
+    ('2003-04-12 04:05:08 America/New_York', 3.0),
+    ('2003-04-12 04:05:09 America/New_York', 4.0),
+    ('2003-04-12 04:05:10 America/New_York', 5.0)
+    ;
+
+    WITH t AS (
+      SELECT
+        time_bucket('1 day'::interval, time) AS dt,
+        stats_agg(value) AS stats
+      FROM sth
+      GROUP BY time_bucket('1 day'::interval, time)
+    )
+    SELECT
+      average(stats)
+    FROM t;
+  '';
+  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ typetetris ];
+    };
+
+    nodes.machine = { ... }:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          extraPlugins = with postgresql-package.pkgs; [
+            timescaledb
+            timescaledb_toolkit
+          ];
+          settings = { shared_preload_libraries = "timescaledb, timescaledb_toolkit"; };
+        };
+      };
+
+    testScript = ''
+      def check_count(statement, lines):
+          return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format(
+              statement, lines
+          )
+
+
+      machine.start()
+      machine.wait_for_unit("postgresql")
+
+      with subtest("Postgresql with extensions timescaledb and timescaledb_toolkit is available just after unit start"):
+          machine.succeed(
+              "sudo -u postgres psql -f ${test-sql}"
+          )
+
+      machine.fail(check_count("SELECT * FROM sth;", 3))
+      machine.succeed(check_count("SELECT * FROM sth;", 5))
+      machine.fail(check_count("SELECT * FROM sth;", 4))
+
+      machine.shutdown()
+    '';
+
+  };
+  applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12") postgresql-versions;
+in
+mapAttrs'
+  (name: package: {
+    inherit name;
+    value = make-postgresql-test name package;
+  })
+  applicablePostgresqlVersions