summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
authorJan Tojnar <jtojnar@gmail.com>2022-12-25 01:30:47 +0100
committerJan Tojnar <jtojnar@gmail.com>2022-12-25 01:30:47 +0100
commit72c37eddec230b691dee4a6c7aed17028a617ad8 (patch)
treecaf36a6707f07dff9fce175e4fc0f6dd8260a24c /nixos/modules
parent4b4777638c424fa29224330d078b065503806aad (diff)
parent3fc0495cdc63449e1699db7b377962fb4ae55190 (diff)
downloadnixpkgs-72c37eddec230b691dee4a6c7aed17028a617ad8.tar
nixpkgs-72c37eddec230b691dee4a6c7aed17028a617ad8.tar.gz
nixpkgs-72c37eddec230b691dee4a6c7aed17028a617ad8.tar.bz2
nixpkgs-72c37eddec230b691dee4a6c7aed17028a617ad8.tar.lz
nixpkgs-72c37eddec230b691dee4a6c7aed17028a617ad8.tar.xz
nixpkgs-72c37eddec230b691dee4a6c7aed17028a617ad8.tar.zst
nixpkgs-72c37eddec230b691dee4a6c7aed17028a617ad8.zip
Merge branch 'staging-next' into staging
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/config/no-x-libs.nix4
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix10
-rw-r--r--nixos/modules/module-list.nix3
-rw-r--r--nixos/modules/profiles/keys/ssh_host_ed25519_key7
-rw-r--r--nixos/modules/profiles/keys/ssh_host_ed25519_key.pub1
-rw-r--r--nixos/modules/profiles/macos-builder.nix140
-rw-r--r--nixos/modules/programs/git.nix38
-rw-r--r--nixos/modules/programs/i3lock.nix58
-rw-r--r--nixos/modules/services/blockchain/ethereum/lighthouse.nix2
-rw-r--r--nixos/modules/services/display-managers/greetd.nix2
-rw-r--r--nixos/modules/services/misc/nitter.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix1
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/statsd.nix19
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.nix7
-rw-r--r--nixos/modules/services/networking/headscale.nix761
-rw-r--r--nixos/modules/services/networking/rpcbind.nix10
-rw-r--r--nixos/modules/services/networking/tox-node.nix2
-rw-r--r--nixos/modules/services/security/aesmd.nix21
-rw-r--r--nixos/modules/services/web-apps/dolibarr.nix19
-rw-r--r--nixos/modules/services/web-apps/peertube.nix121
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix49
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix2
-rw-r--r--nixos/modules/system/activation/bootspec.cue1
-rw-r--r--nixos/modules/system/activation/bootspec.nix12
-rw-r--r--nixos/modules/system/activation/top-level.nix2
-rw-r--r--nixos/modules/system/boot/stage-1.nix5
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix4
-rw-r--r--nixos/modules/virtualisation/podman/default.nix48
30 files changed, 915 insertions, 442 deletions
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 3efadea8235..70e265a65a6 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -33,9 +33,13 @@ with lib;
       ffmpeg_4 = super.ffmpeg_4-headless;
       ffmpeg_5 = super.ffmpeg_5-headless;
       gobject-introspection = super.gobject-introspection.override { x11Support = false; };
+      gpsd = super.gpsd.override { guiSupport = false; };
       imagemagick = super.imagemagick.override { libX11Support = false; libXtSupport = false; };
       imagemagickBig = super.imagemagickBig.override { libX11Support = false; libXtSupport = false; };
+      libextractor = super.libextractor.override { gstreamerSupport = false; gtkSupport = false; };
       libva = super.libva-minimal;
+      limesuite = super.limesuite.override { withGui = false; };
+      msmtp = super.msmtp.override { withKeyring = false; };
       networkmanager-fortisslvpn = super.networkmanager-fortisslvpn.override { withGnome = false; };
       networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
       networkmanager-l2tp = super.networkmanager-l2tp.override { withGnome = false; };
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 5bd343c85fa..81aca861738 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -70,14 +70,12 @@ let
   ;
 
   # Timeout in syslinux is in units of 1/10 of a second.
-  # 0 is used to disable timeouts.
+  # null means max timeout (35996, just under 1h in 1/10 seconds)
+  # 0 means disable timeout
   syslinuxTimeout = if config.boot.loader.timeout == null then
-      0
+      35996
     else
-      max (config.boot.loader.timeout * 10) 1;
-
-
-  max = x: y: if x > y then x else y;
+      config.boot.loader.timeout * 10;
 
   # The configuration file for syslinux.
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index cfd83e2ecd1..aa09af6ec29 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -180,6 +180,7 @@
   ./programs/hamster.nix
   ./programs/htop.nix
   ./programs/iftop.nix
+  ./programs/i3lock.nix
   ./programs/iotop.nix
   ./programs/java.nix
   ./programs/k3b.nix
@@ -724,6 +725,7 @@
   ./services/monitoring/riemann.nix
   ./services/monitoring/scollector.nix
   ./services/monitoring/smartd.nix
+  ./services/monitoring/statsd.nix
   ./services/monitoring/sysstat.nix
   ./services/monitoring/teamviewer.nix
   ./services/monitoring/telegraf.nix
@@ -874,7 +876,6 @@
   ./services/networking/miredo.nix
   ./services/networking/mjpg-streamer.nix
   ./services/networking/mmsd.nix
-  ./services/networking/mosquitto.nix
   ./services/networking/monero.nix
   ./services/networking/morty.nix
   ./services/networking/mosquitto.nix
diff --git a/nixos/modules/profiles/keys/ssh_host_ed25519_key b/nixos/modules/profiles/keys/ssh_host_ed25519_key
new file mode 100644
index 00000000000..b1848979536
--- /dev/null
+++ b/nixos/modules/profiles/keys/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCQVnMW/wZWqrdWrjrRPhfEFFq1KLYguagSflLhFnVQmwAAAJASuMMnErjD
+JwAAAAtzc2gtZWQyNTUxOQAAACCQVnMW/wZWqrdWrjrRPhfEFFq1KLYguagSflLhFnVQmw
+AAAEDIN2VWFyggtoSPXcAFy8dtG1uAig8sCuyE21eMDt2GgJBWcxb/Blaqt1auOtE+F8QU
+WrUotiC5qBJ+UuEWdVCbAAAACnJvb3RAbml4b3MBAgM=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub b/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub
new file mode 100644
index 00000000000..2c45826715f
--- /dev/null
+++ b/nixos/modules/profiles/keys/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBWcxb/Blaqt1auOtE+F8QUWrUotiC5qBJ+UuEWdVCb root@nixos
diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix
new file mode 100644
index 00000000000..0cbac3bd61f
--- /dev/null
+++ b/nixos/modules/profiles/macos-builder.nix
@@ -0,0 +1,140 @@
+{ config, lib, pkgs, ... }:
+
+let
+  keysDirectory = "/var/keys";
+
+  user = "builder";
+
+  keyType = "ed25519";
+
+in
+
+{ imports = [
+    ../virtualisation/qemu-vm.nix
+  ];
+
+  # The builder is not intended to be used interactively
+  documentation.enable = false;
+
+  environment.etc = {
+    "ssh/ssh_host_ed25519_key" = {
+      mode = "0600";
+
+      source = ./keys/ssh_host_ed25519_key;
+    };
+
+    "ssh/ssh_host_ed25519_key.pub" = {
+      mode = "0644";
+
+      source = ./keys/ssh_host_ed25519_key.pub;
+    };
+  };
+
+  # DNS fails for QEMU user networking (SLiRP) on macOS.  See:
+  #
+  # https://github.com/utmapp/UTM/issues/2353
+  #
+  # This works around that by using a public DNS server other than the DNS
+  # server that QEMU provides (normally 10.0.2.3)
+  networking.nameservers = [ "8.8.8.8" ];
+
+  nix.settings = {
+    auto-optimise-store = true;
+
+    min-free = 1024 * 1024 * 1024;
+
+    max-free = 3 * 1024 * 1024 * 1024;
+
+    trusted-users = [ "root" user ];
+  };
+
+  services.openssh = {
+    enable = true;
+
+    authorizedKeysFiles = [ "${keysDirectory}/%u_${keyType}.pub" ];
+  };
+
+  system.build.macos-builder-installer =
+    let
+      privateKey = "/etc/nix/${user}_${keyType}";
+
+      publicKey = "${privateKey}.pub";
+
+      # This installCredentials script is written so that it's as easy as
+      # possible for a user to audit before confirming the `sudo`
+      installCredentials = hostPkgs.writeShellScript "install-credentials" ''
+        KEYS="''${1}"
+        INSTALL=${hostPkgs.coreutils}/bin/install
+        "''${INSTALL}" -g nixbld -m 600 "''${KEYS}/${user}_${keyType}" ${privateKey}
+        "''${INSTALL}" -g nixbld -m 644 "''${KEYS}/${user}_${keyType}.pub" ${publicKey}
+      '';
+
+      hostPkgs = config.virtualisation.host.pkgs;
+
+      script = hostPkgs.writeShellScriptBin "create-builder" ''
+        KEYS="''${KEYS:-./keys}"
+        ${hostPkgs.coreutils}/bin/mkdir --parent "''${KEYS}"
+        PRIVATE_KEY="''${KEYS}/${user}_${keyType}"
+        PUBLIC_KEY="''${PRIVATE_KEY}.pub"
+        if [ ! -e "''${PRIVATE_KEY}" ] || [ ! -e "''${PUBLIC_KEY}" ]; then
+            ${hostPkgs.coreutils}/bin/rm --force -- "''${PRIVATE_KEY}" "''${PUBLIC_KEY}"
+            ${hostPkgs.openssh}/bin/ssh-keygen -q -f "''${PRIVATE_KEY}" -t ${keyType} -N "" -C 'builder@localhost'
+        fi
+        if ! ${hostPkgs.diffutils}/bin/cmp "''${PUBLIC_KEY}" ${publicKey}; then
+          (set -x; sudo --reset-timestamp ${installCredentials} "''${KEYS}")
+        fi
+        KEYS="$(nix-store --add "$KEYS")" ${config.system.build.vm}/bin/run-nixos-vm
+      '';
+
+    in
+      script.overrideAttrs (old: {
+        meta = (old.meta or { }) // {
+          platforms = lib.platforms.darwin;
+        };
+      });
+
+  system.stateVersion = "22.05";
+
+  users.users."${user}"= {
+    isNormalUser = true;
+  };
+
+  virtualisation = {
+    diskSize = 20 * 1024;
+
+    memorySize = 3 * 1024;
+
+    forwardPorts = [
+      { from = "host"; guest.port = 22; host.port = 22; }
+    ];
+
+    # Disable graphics for the builder since users will likely want to run it
+    # non-interactively in the background.
+    graphics = false;
+
+    sharedDirectories.keys = {
+      source = "\"$KEYS\"";
+      target = keysDirectory;
+    };
+
+    # If we don't enable this option then the host will fail to delegate builds
+    # to the guest, because:
+    #
+    # - The host will lock the path to build
+    # - The host will delegate the build to the guest
+    # - The guest will attempt to lock the same path and fail because
+    #   the lockfile on the host is visible on the guest
+    #
+    # Snapshotting the host's /nix/store as an image isolates the guest VM's
+    # /nix/store from the host's /nix/store, preventing this problem.
+    useNixStoreImage = true;
+
+    # Obviously the /nix/store needs to be writable on the guest in order for it
+    # to perform builds.
+    writableStore = true;
+
+    # This ensures that anything built on the guest isn't lost when the guest is
+    # restarted.
+    writableStoreUseTmpfs = false;
+  };
+}
diff --git a/nixos/modules/programs/git.nix b/nixos/modules/programs/git.nix
index acff5dfdd88..4e271a8c134 100644
--- a/nixos/modules/programs/git.nix
+++ b/nixos/modules/programs/git.nix
@@ -20,15 +20,41 @@ in
       };
 
       config = mkOption {
-        type = with types; attrsOf (attrsOf anything);
-        default = { };
+        type =
+          with types;
+          let
+            gitini = attrsOf (attrsOf anything);
+          in
+          either gitini (listOf gitini) // {
+            merge = loc: defs:
+              let
+                config = foldl'
+                  (acc: { value, ... }@x: acc // (if isList value then {
+                    ordered = acc.ordered ++ value;
+                  } else {
+                    unordered = acc.unordered ++ [ x ];
+                  }))
+                  {
+                    ordered = [ ];
+                    unordered = [ ];
+                  }
+                  defs;
+              in
+              [ (gitini.merge loc config.unordered) ] ++ config.ordered;
+          };
+        default = [ ];
         example = {
           init.defaultBranch = "main";
           url."https://github.com/".insteadOf = [ "gh:" "github:" ];
         };
         description = lib.mdDoc ''
-          Configuration to write to /etc/gitconfig. See the CONFIGURATION FILE
-          section of git-config(1) for more information.
+          Configuration to write to /etc/gitconfig. A list can also be
+          specified to keep the configuration in order. For example, setting
+          `config` to `[ { foo.x = 42; } { bar.y = 42; }]` will put the `foo`
+          section before the `bar` section unlike the default alphabetical
+          order, which can be helpful for sections such as `include` and
+          `includeIf`. See the CONFIGURATION FILE section of git-config(1) for
+          more information.
         '';
       };
 
@@ -48,8 +74,8 @@ in
   config = mkMerge [
     (mkIf cfg.enable {
       environment.systemPackages = [ cfg.package ];
-      environment.etc.gitconfig = mkIf (cfg.config != {}) {
-        text = generators.toGitINI cfg.config;
+      environment.etc.gitconfig = mkIf (cfg.config != [ ]) {
+        text = concatMapStringsSep "\n" generators.toGitINI cfg.config;
       };
     })
     (mkIf (cfg.enable && cfg.lfs.enable) {
diff --git a/nixos/modules/programs/i3lock.nix b/nixos/modules/programs/i3lock.nix
new file mode 100644
index 00000000000..466ae59c927
--- /dev/null
+++ b/nixos/modules/programs/i3lock.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs.i3lock;
+
+in {
+
+  ###### interface
+
+  options = {
+    programs.i3lock = {
+      enable = mkEnableOption (mdDoc "i3lock");
+      package = mkOption {
+        type        = types.package;
+        default     = pkgs.i3lock;
+        defaultText = literalExpression "pkgs.i3lock";
+        example     = literalExpression ''
+          pkgs.i3lock-color
+        '';
+        description = mdDoc ''
+          Specify which package to use for the i3lock program,
+          The i3lock package must include a i3lock file or link in its out directory in order for the u2fSupport option to work correctly.
+        '';
+      };
+      u2fSupport = mkOption {
+        type        = types.bool;
+        default     = false;
+        example     = true;
+        description = mdDoc ''
+          Whether to enable U2F support in the i3lock program.
+          U2F enables authentication using a hardware device, such as a security key.
+          When U2F support is enabled, the i3lock program will set the setuid bit on the i3lock binary and enable the pam u2fAuth service,
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    security.wrappers.i3lock = mkIf cfg.u2fSupport {
+      setuid = true;
+      owner = "root";
+      group = "root";
+      source = "${cfg.package.out}/bin/i3lock";
+    };
+
+    security.pam.services.i3lock.u2fAuth = cfg.u2fSupport;
+
+  };
+
+}
diff --git a/nixos/modules/services/blockchain/ethereum/lighthouse.nix b/nixos/modules/services/blockchain/ethereum/lighthouse.nix
index 96d66e44d16..863e737d908 100644
--- a/nixos/modules/services/blockchain/ethereum/lighthouse.nix
+++ b/nixos/modules/services/blockchain/ethereum/lighthouse.nix
@@ -280,7 +280,7 @@ in {
         ${pkgs.lighthouse}/bin/lighthouse validator_client \
           --network ${cfg.network} \
           --beacon-nodes ${lib.concatStringsSep "," cfg.validator.beaconNodes} \
-          --datadir ${cfg.validator.dataDir}/${cfg.network}
+          --datadir ${cfg.validator.dataDir}/${cfg.network} \
           ${optionalString cfg.validator.metrics.enable ''--metrics --metrics-address ${cfg.validator.metrics.address} --metrics-port ${toString cfg.validator.metrics.port}''} \
           ${cfg.extraArgs} ${cfg.validator.extraArgs}
       '';
diff --git a/nixos/modules/services/display-managers/greetd.nix b/nixos/modules/services/display-managers/greetd.nix
index 96eddc1ce7c..3a0f59f62af 100644
--- a/nixos/modules/services/display-managers/greetd.nix
+++ b/nixos/modules/services/display-managers/greetd.nix
@@ -89,6 +89,8 @@ in
         SendSIGHUP = true;
         TimeoutStopSec = "30s";
         KeyringMode = "shared";
+
+        Type = "idle";
       };
 
       # Don't kill a user session when using nixos-rebuild
diff --git a/nixos/modules/services/misc/nitter.nix b/nixos/modules/services/misc/nitter.nix
index 95394d9d211..f0cb5cc1513 100644
--- a/nixos/modules/services/misc/nitter.nix
+++ b/nixos/modules/services/misc/nitter.nix
@@ -47,7 +47,7 @@ in
 {
   options = {
     services.nitter = {
-      enable = mkEnableOption (lib.mdDoc "If enabled, start Nitter.");
+      enable = mkEnableOption (lib.mdDoc "Nitter");
 
       package = mkOption {
         default = pkgs.nitter;
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 5061666f57c..9a9a0ab7553 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -555,7 +555,7 @@ in {
             auto_assign_org_role = mkOption {
               description = lib.mdDoc "Default role new users will be auto assigned.";
               default = "Viewer";
-              type = types.enum ["Viewer" "Editor"];
+              type = types.enum ["Viewer" "Editor" "Admin"];
             };
           };
 
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 2451f46ba7d..f3fbfb149ad 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -68,6 +68,7 @@ let
     "smartctl"
     "smokeping"
     "sql"
+    "statsd"
     "surfboard"
     "systemd"
     "tor"
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix b/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix
new file mode 100644
index 00000000000..d9d732d8c12
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/statsd.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.statsd;
+in
+{
+  port = 9102;
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-statsd-exporter}/bin/statsd_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix
index c3d9f43f745..9733fb16d90 100644
--- a/nixos/modules/services/networking/firefox-syncserver.nix
+++ b/nixos/modules/services/networking/firefox-syncserver.nix
@@ -11,8 +11,10 @@ let
 
   format = pkgs.formats.toml {};
   settings = {
-    database_url = dbURL;
     human_logs = true;
+    syncstorage = {
+      database_url = dbURL;
+    };
     tokenserver = {
       node_type = "mysql";
       database_url = dbURL;
@@ -253,8 +255,7 @@ in
       serviceConfig = {
         User = defaultUser;
         Group = defaultUser;
-        ExecStart = "${cfg.package}/bin/syncstorage --config ${configFile}";
-        Stderr = "journal";
+        ExecStart = "${cfg.package}/bin/syncserver --config ${configFile}";
         EnvironmentFile = lib.mkIf (cfg.secrets != null) "${cfg.secrets}";
 
         # hardening
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index 29b632ff5d2..cc46819eed5 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -1,15 +1,18 @@
-{ config, lib, pkgs, ... }:
-with lib;
-let
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
   cfg = config.services.headscale;
 
   dataDir = "/var/lib/headscale";
   runDir = "/run/headscale";
 
-  settingsFormat = pkgs.formats.yaml { };
+  settingsFormat = pkgs.formats.yaml {};
   configFile = settingsFormat.generate "headscale.yaml" cfg.settings;
-in
-{
+in {
   options = {
     services.headscale = {
       enable = mkEnableOption (lib.mdDoc "headscale, Open Source coordination server for Tailscale");
@@ -51,15 +54,6 @@ in
         '';
       };
 
-      serverUrl = mkOption {
-        type = types.str;
-        default = "http://127.0.0.1:8080";
-        description = lib.mdDoc ''
-          The url clients will connect to.
-        '';
-        example = "https://myheadscale.example.com:443";
-      };
-
       address = mkOption {
         type = types.str;
         default = "127.0.0.1";
@@ -78,337 +72,346 @@ in
         example = 443;
       };
 
-      privateKeyFile = mkOption {
-        type = types.path;
-        default = "${dataDir}/private.key";
-        description = lib.mdDoc ''
-          Path to private key file, generated automatically if it does not exist.
-        '';
-      };
-
-      derp = {
-        urls = mkOption {
-          type = types.listOf types.str;
-          default = [ "https://controlplane.tailscale.com/derpmap/default" ];
-          description = lib.mdDoc ''
-            List of urls containing DERP maps.
-            See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
-          '';
-        };
-
-        paths = mkOption {
-          type = types.listOf types.path;
-          default = [ ];
-          description = lib.mdDoc ''
-            List of file paths containing DERP maps.
-            See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
-          '';
-        };
-
-
-        autoUpdate = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to automatically update DERP maps on a set frequency.
-          '';
-          example = false;
-        };
-
-        updateFrequency = mkOption {
-          type = types.str;
-          default = "24h";
-          description = lib.mdDoc ''
-            Frequency to update DERP maps.
-          '';
-          example = "5m";
-        };
-
-      };
-
-      ephemeralNodeInactivityTimeout = mkOption {
-        type = types.str;
-        default = "30m";
-        description = lib.mdDoc ''
-          Time before an inactive ephemeral node is deleted.
-        '';
-        example = "5m";
-      };
-
-      database = {
-        type = mkOption {
-          type = types.enum [ "sqlite3" "postgres" ];
-          example = "postgres";
-          default = "sqlite3";
-          description = lib.mdDoc "Database engine to use.";
-        };
-
-        host = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "127.0.0.1";
-          description = lib.mdDoc "Database host address.";
-        };
-
-        port = mkOption {
-          type = types.nullOr types.port;
-          default = null;
-          example = 3306;
-          description = lib.mdDoc "Database host port.";
-        };
-
-        name = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "headscale";
-          description = lib.mdDoc "Database name.";
-        };
-
-        user = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "headscale";
-          description = lib.mdDoc "Database user.";
-        };
-
-        passwordFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          example = "/run/keys/headscale-dbpassword";
-          description = lib.mdDoc ''
-            A file containing the password corresponding to
-            {option}`database.user`.
-          '';
-        };
-
-        path = mkOption {
-          type = types.nullOr types.str;
-          default = "${dataDir}/db.sqlite";
-          description = lib.mdDoc "Path to the sqlite3 database file.";
-        };
-      };
-
-      logLevel = mkOption {
-        type = types.str;
-        default = "info";
-        description = lib.mdDoc ''
-          headscale log level.
-        '';
-        example = "debug";
-      };
-
-      dns = {
-        nameservers = mkOption {
-          type = types.listOf types.str;
-          default = [ "1.1.1.1" ];
-          description = lib.mdDoc ''
-            List of nameservers to pass to Tailscale clients.
-          '';
-        };
-
-        domains = mkOption {
-          type = types.listOf types.str;
-          default = [ ];
-          description = lib.mdDoc ''
-            Search domains to inject to Tailscale clients.
-          '';
-          example = [ "mydomain.internal" ];
-        };
-
-        magicDns = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
-            Only works if there is at least a nameserver defined.
-          '';
-          example = false;
-        };
-
-        baseDomain = mkOption {
-          type = types.str;
-          default = "";
-          description = lib.mdDoc ''
-            Defines the base domain to create the hostnames for MagicDNS.
-            {option}`baseDomain` must be a FQDNs, without the trailing dot.
-            The FQDN of the hosts will be
-            `hostname.namespace.base_domain` (e.g.
-            `myhost.mynamespace.example.com`).
-          '';
-        };
-      };
-
-      openIdConnect = {
-        issuer = mkOption {
-          type = types.str;
-          default = "";
-          description = lib.mdDoc ''
-            URL to OpenID issuer.
-          '';
-          example = "https://openid.example.com";
-        };
-
-        clientId = mkOption {
-          type = types.str;
-          default = "";
-          description = lib.mdDoc ''
-            OpenID Connect client ID.
-          '';
-        };
-
-        clientSecretFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = lib.mdDoc ''
-            Path to OpenID Connect client secret file.
-          '';
-        };
-
-        domainMap = mkOption {
-          type = types.attrsOf types.str;
-          default = { };
-          description = lib.mdDoc ''
-            Domain map is used to map incoming users (by their email) to
-            a namespace. The key can be a string, or regex.
-          '';
-          example = {
-            ".*" = "default-namespace";
-          };
-        };
-
-      };
-
-      tls = {
-        letsencrypt = {
-          hostname = mkOption {
-            type = types.nullOr types.str;
-            default = "";
-            description = lib.mdDoc ''
-              Domain name to request a TLS certificate for.
-            '';
-          };
-          challengeType = mkOption {
-            type = types.enum [ "TLS-ALPN-01" "HTTP-01" ];
-            default = "HTTP-01";
-            description = lib.mdDoc ''
-              Type of ACME challenge to use, currently supported types:
-              `HTTP-01` or `TLS-ALPN-01`.
-            '';
-          };
-          httpListen = mkOption {
-            type = types.nullOr types.str;
-            default = ":http";
-            description = lib.mdDoc ''
-              When HTTP-01 challenge is chosen, letsencrypt must set up a
-              verification endpoint, and it will be listening on:
-              `:http = port 80`.
-            '';
-          };
-        };
-
-        certFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = lib.mdDoc ''
-            Path to already created certificate.
-          '';
-        };
-        keyFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = lib.mdDoc ''
-            Path to key for already created certificate.
-          '';
-        };
-      };
-
-      aclPolicyFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = lib.mdDoc ''
-          Path to a file containing ACL policies.
-        '';
-      };
-
       settings = mkOption {
-        type = settingsFormat.type;
-        default = { };
         description = lib.mdDoc ''
           Overrides to {file}`config.yaml` as a Nix attribute set.
-          This option is ideal for overriding settings not exposed as Nix options.
           Check the [example config](https://github.com/juanfont/headscale/blob/main/config-example.yaml)
           for possible options.
         '';
+        type = types.submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            server_url = mkOption {
+              type = types.str;
+              default = "http://127.0.0.1:8080";
+              description = lib.mdDoc ''
+                The url clients will connect to.
+              '';
+              example = "https://myheadscale.example.com:443";
+            };
+
+            private_key_path = mkOption {
+              type = types.path;
+              default = "${dataDir}/private.key";
+              description = lib.mdDoc ''
+                Path to private key file, generated automatically if it does not exist.
+              '';
+            };
+
+            noise.private_key_path = mkOption {
+              type = types.path;
+              default = "${dataDir}/noise_private.key";
+              description = lib.mdDoc ''
+                Path to noise private key file, generated automatically if it does not exist.
+              '';
+            };
+
+            derp = {
+              urls = mkOption {
+                type = types.listOf types.str;
+                default = ["https://controlplane.tailscale.com/derpmap/default"];
+                description = lib.mdDoc ''
+                  List of urls containing DERP maps.
+                  See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
+                '';
+              };
+
+              paths = mkOption {
+                type = types.listOf types.path;
+                default = [];
+                description = lib.mdDoc ''
+                  List of file paths containing DERP maps.
+                  See [How Tailscale works](https://tailscale.com/blog/how-tailscale-works/) for more information on DERP maps.
+                '';
+              };
+
+              auto_update_enable = mkOption {
+                type = types.bool;
+                default = true;
+                description = lib.mdDoc ''
+                  Whether to automatically update DERP maps on a set frequency.
+                '';
+                example = false;
+              };
+
+              update_frequency = mkOption {
+                type = types.str;
+                default = "24h";
+                description = lib.mdDoc ''
+                  Frequency to update DERP maps.
+                '';
+                example = "5m";
+              };
+            };
+
+            ephemeral_node_inactivity_timeout = mkOption {
+              type = types.str;
+              default = "30m";
+              description = lib.mdDoc ''
+                Time before an inactive ephemeral node is deleted.
+              '';
+              example = "5m";
+            };
+
+            db_type = mkOption {
+              type = types.enum ["sqlite3" "postgres"];
+              example = "postgres";
+              default = "sqlite3";
+              description = lib.mdDoc "Database engine to use.";
+            };
+
+            db_host = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "127.0.0.1";
+              description = lib.mdDoc "Database host address.";
+            };
+
+            db_port = mkOption {
+              type = types.nullOr types.port;
+              default = null;
+              example = 3306;
+              description = lib.mdDoc "Database host port.";
+            };
+
+            db_name = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "headscale";
+              description = lib.mdDoc "Database name.";
+            };
+
+            db_user = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "headscale";
+              description = lib.mdDoc "Database user.";
+            };
+
+            db_password_file = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              example = "/run/keys/headscale-dbpassword";
+              description = lib.mdDoc ''
+                A file containing the password corresponding to
+                {option}`database.user`.
+              '';
+            };
+
+            db_path = mkOption {
+              type = types.nullOr types.str;
+              default = "${dataDir}/db.sqlite";
+              description = lib.mdDoc "Path to the sqlite3 database file.";
+            };
+
+            log.level = mkOption {
+              type = types.str;
+              default = "info";
+              description = lib.mdDoc ''
+                headscale log level.
+              '';
+              example = "debug";
+            };
+
+            log.format = mkOption {
+              type = types.str;
+              default = "text";
+              description = lib.mdDoc ''
+                headscale log format.
+              '';
+              example = "json";
+            };
+
+            dns_config = {
+              nameservers = mkOption {
+                type = types.listOf types.str;
+                default = ["1.1.1.1"];
+                description = lib.mdDoc ''
+                  List of nameservers to pass to Tailscale clients.
+                '';
+              };
+
+              override_local_dns = mkOption {
+                type = types.bool;
+                default = false;
+                description = lib.mdDoc ''
+                  Whether to use [Override local DNS](https://tailscale.com/kb/1054/dns/).
+                '';
+                example = true;
+              };
+
+              domains = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = lib.mdDoc ''
+                  Search domains to inject to Tailscale clients.
+                '';
+                example = ["mydomain.internal"];
+              };
+
+              magic_dns = mkOption {
+                type = types.bool;
+                default = true;
+                description = lib.mdDoc ''
+                  Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
+                  Only works if there is at least a nameserver defined.
+                '';
+                example = false;
+              };
+
+              base_domain = mkOption {
+                type = types.str;
+                default = "";
+                description = lib.mdDoc ''
+                  Defines the base domain to create the hostnames for MagicDNS.
+                  {option}`baseDomain` must be a FQDNs, without the trailing dot.
+                  The FQDN of the hosts will be
+                  `hostname.namespace.base_domain` (e.g.
+                  `myhost.mynamespace.example.com`).
+                '';
+              };
+            };
+
+            oidc = {
+              issuer = mkOption {
+                type = types.str;
+                default = "";
+                description = lib.mdDoc ''
+                  URL to OpenID issuer.
+                '';
+                example = "https://openid.example.com";
+              };
+
+              client_id = mkOption {
+                type = types.str;
+                default = "";
+                description = lib.mdDoc ''
+                  OpenID Connect client ID.
+                '';
+              };
+
+              client_secret_file = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                description = lib.mdDoc ''
+                  Path to OpenID Connect client secret file.
+                '';
+              };
+
+              domain_map = mkOption {
+                type = types.attrsOf types.str;
+                default = {};
+                description = lib.mdDoc ''
+                  Domain map is used to map incomming users (by their email) to
+                  a namespace. The key can be a string, or regex.
+                '';
+                example = {
+                  ".*" = "default-namespace";
+                };
+              };
+            };
+
+            tls_letsencrypt_hostname = mkOption {
+              type = types.nullOr types.str;
+              default = "";
+              description = lib.mdDoc ''
+                Domain name to request a TLS certificate for.
+              '';
+            };
+
+            tls_letsencrypt_challenge_type = mkOption {
+              type = types.enum ["TLS-ALPN-01" "HTTP-01"];
+              default = "HTTP-01";
+              description = lib.mdDoc ''
+                Type of ACME challenge to use, currently supported types:
+                `HTTP-01` or `TLS-ALPN-01`.
+              '';
+            };
+
+            tls_letsencrypt_listen = mkOption {
+              type = types.nullOr types.str;
+              default = ":http";
+              description = lib.mdDoc ''
+                When HTTP-01 challenge is chosen, letsencrypt must set up a
+                verification endpoint, and it will be listening on:
+                `:http = port 80`.
+              '';
+            };
+
+            tls_cert_path = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = lib.mdDoc ''
+                Path to already created certificate.
+              '';
+            };
+
+            tls_key_path = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = lib.mdDoc ''
+                Path to key for already created certificate.
+              '';
+            };
+
+            acl_policy_path = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = lib.mdDoc ''
+                Path to a file containg ACL policies.
+              '';
+            };
+          };
+        };
       };
-
-
     };
-
   };
-  config = mkIf cfg.enable {
 
+  imports = [
+    # TODO address + port = listen_addr
+    (mkRenamedOptionModule ["services" "headscale" "serverUrl"] ["services" "headscale" "settings" "server_url"])
+    (mkRenamedOptionModule ["services" "headscale" "privateKeyFile"] ["services" "headscale" "settings" "private_key_path"])
+    (mkRenamedOptionModule ["services" "headscale" "derp" "urls"] ["services" "headscale" "settings" "derp" "urls"])
+    (mkRenamedOptionModule ["services" "headscale" "derp" "paths"] ["services" "headscale" "settings" "derp" "paths"])
+    (mkRenamedOptionModule ["services" "headscale" "derp" "autoUpdate"] ["services" "headscale" "settings" "derp" "auto_update_enable"])
+    (mkRenamedOptionModule ["services" "headscale" "derp" "updateFrequency"] ["services" "headscale" "settings" "derp" "update_frequency"])
+    (mkRenamedOptionModule ["services" "headscale" "ephemeralNodeInactivityTimeout"] ["services" "headscale" "settings" "ephemeral_node_inactivity_timeout"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "type"] ["services" "headscale" "settings" "db_type"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "path"] ["services" "headscale" "settings" "db_path"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "host"] ["services" "headscale" "settings" "db_host"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "port"] ["services" "headscale" "settings" "db_port"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "name"] ["services" "headscale" "settings" "db_name"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "user"] ["services" "headscale" "settings" "db_user"])
+    (mkRenamedOptionModule ["services" "headscale" "database" "passwordFile"] ["services" "headscale" "settings" "db_password_file"])
+    (mkRenamedOptionModule ["services" "headscale" "logLevel"] ["services" "headscale" "settings" "log" "level"])
+    (mkRenamedOptionModule ["services" "headscale" "dns" "nameservers"] ["services" "headscale" "settings" "dns_config" "nameservers"])
+    (mkRenamedOptionModule ["services" "headscale" "dns" "domains"] ["services" "headscale" "settings" "dns_config" "domains"])
+    (mkRenamedOptionModule ["services" "headscale" "dns" "magicDns"] ["services" "headscale" "settings" "dns_config" "magic_dns"])
+    (mkRenamedOptionModule ["services" "headscale" "dns" "baseDomain"] ["services" "headscale" "settings" "dns_config" "base_domain"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientId"] ["services" "headscale" "settings" "oidc" "client_id"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_file"])
+    (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "domainMap"] ["services" "headscale" "settings" "oidc" "domain_map"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "certFile"] ["services" "headscale" "settings" "tls_cert_path"])
+    (mkRenamedOptionModule ["services" "headscale" "tls" "keyFile"] ["services" "headscale" "settings" "tls_key_path"])
+    (mkRenamedOptionModule ["services" "headscale" "aclPolicyFile"] ["services" "headscale" "settings" "acl_policy_path"])
+  ];
+
+  config = mkIf cfg.enable {
     services.headscale.settings = {
-      server_url = mkDefault cfg.serverUrl;
       listen_addr = mkDefault "${cfg.address}:${toString cfg.port}";
 
-      private_key_path = mkDefault cfg.privateKeyFile;
-
-      derp = {
-        urls = mkDefault cfg.derp.urls;
-        paths = mkDefault cfg.derp.paths;
-        auto_update_enable = mkDefault cfg.derp.autoUpdate;
-        update_frequency = mkDefault cfg.derp.updateFrequency;
-      };
-
       # Turn off update checks since the origin of our package
       # is nixpkgs and not Github.
       disable_check_updates = true;
 
-      ephemeral_node_inactivity_timeout = mkDefault cfg.ephemeralNodeInactivityTimeout;
-
-      db_type = mkDefault cfg.database.type;
-      db_path = mkDefault cfg.database.path;
-
-      log_level = mkDefault cfg.logLevel;
-
-      dns_config = {
-        nameservers = mkDefault cfg.dns.nameservers;
-        domains = mkDefault cfg.dns.domains;
-        magic_dns = mkDefault cfg.dns.magicDns;
-        base_domain = mkDefault cfg.dns.baseDomain;
-      };
-
       unix_socket = "${runDir}/headscale.sock";
 
-      # OpenID Connect
-      oidc = {
-        issuer = mkDefault cfg.openIdConnect.issuer;
-        client_id = mkDefault cfg.openIdConnect.clientId;
-        domain_map = mkDefault cfg.openIdConnect.domainMap;
-      };
-
       tls_letsencrypt_cache_dir = "${dataDir}/.cache";
-
-    } // optionalAttrs (cfg.database.host != null) {
-      db_host = mkDefault cfg.database.host;
-    } // optionalAttrs (cfg.database.port != null) {
-      db_port = mkDefault cfg.database.port;
-    } // optionalAttrs (cfg.database.name != null) {
-      db_name = mkDefault cfg.database.name;
-    } // optionalAttrs (cfg.database.user != null) {
-      db_user = mkDefault cfg.database.user;
-    } // optionalAttrs (cfg.tls.letsencrypt.hostname != null) {
-      tls_letsencrypt_hostname = mkDefault cfg.tls.letsencrypt.hostname;
-    } // optionalAttrs (cfg.tls.letsencrypt.challengeType != null) {
-      tls_letsencrypt_challenge_type = mkDefault cfg.tls.letsencrypt.challengeType;
-    } // optionalAttrs (cfg.tls.letsencrypt.httpListen != null) {
-      tls_letsencrypt_listen = mkDefault cfg.tls.letsencrypt.httpListen;
-    } // optionalAttrs (cfg.tls.certFile != null) {
-      tls_cert_path = mkDefault cfg.tls.certFile;
-    } // optionalAttrs (cfg.tls.keyFile != null) {
-      tls_key_path = mkDefault cfg.tls.keyFile;
-    } // optionalAttrs (cfg.aclPolicyFile != null) {
-      acl_policy_path = mkDefault cfg.aclPolicyFile;
     };
 
     # Setup the headscale configuration in a known path in /etc to
@@ -416,7 +419,7 @@ in
     # for communication.
     environment.etc."headscale/config.yaml".source = configFile;
 
-    users.groups.headscale = mkIf (cfg.group == "headscale") { };
+    users.groups.headscale = mkIf (cfg.group == "headscale") {};
 
     users.users.headscale = mkIf (cfg.user == "headscale") {
       description = "headscale user";
@@ -427,70 +430,68 @@ in
 
     systemd.services.headscale = {
       description = "headscale coordination server for Tailscale";
-      after = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-      restartTriggers = [ configFile ];
+      after = ["network-online.target"];
+      wantedBy = ["multi-user.target"];
+      restartTriggers = [configFile];
 
       environment.GIN_MODE = "release";
 
       script = ''
-        ${optionalString (cfg.database.passwordFile != null) ''
-          export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.database.passwordFile})"
+        ${optionalString (cfg.settings.db_password_file != null) ''
+          export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.settings.db_password_file})"
         ''}
 
-        ${optionalString (cfg.openIdConnect.clientSecretFile != null) ''
-          export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.openIdConnect.clientSecretFile})"
+        ${optionalString (cfg.settings.oidc.client_secret_file != null) ''
+          export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.settings.oidc.client_secret_file})"
         ''}
         exec ${cfg.package}/bin/headscale serve
       '';
 
-      serviceConfig =
-        let
-          capabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
-        in
-        {
-          Restart = "always";
-          Type = "simple";
-          User = cfg.user;
-          Group = cfg.group;
-
-          # Hardening options
-          RuntimeDirectory = "headscale";
-          # Allow headscale group access so users can be added and use the CLI.
-          RuntimeDirectoryMode = "0750";
-
-          StateDirectory = "headscale";
-          StateDirectoryMode = "0750";
-
-          ProtectSystem = "strict";
-          ProtectHome = true;
-          PrivateTmp = true;
-          PrivateDevices = true;
-          ProtectKernelTunables = true;
-          ProtectControlGroups = true;
-          RestrictSUIDSGID = true;
-          PrivateMounts = true;
-          ProtectKernelModules = true;
-          ProtectKernelLogs = true;
-          ProtectHostname = true;
-          ProtectClock = true;
-          ProtectProc = "invisible";
-          ProcSubset = "pid";
-          RestrictNamespaces = true;
-          RemoveIPC = true;
-          UMask = "0077";
-
-          CapabilityBoundingSet = capabilityBoundingSet;
-          AmbientCapabilities = capabilityBoundingSet;
-          NoNewPrivileges = true;
-          LockPersonality = true;
-          RestrictRealtime = true;
-          SystemCallFilter = [ "@system-service" "~@privileged" "@chown" ];
-          SystemCallArchitectures = "native";
-          RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
-        };
+      serviceConfig = let
+        capabilityBoundingSet = ["CAP_CHOWN"] ++ optional (cfg.port < 1024) "CAP_NET_BIND_SERVICE";
+      in {
+        Restart = "always";
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+
+        # Hardening options
+        RuntimeDirectory = "headscale";
+        # Allow headscale group access so users can be added and use the CLI.
+        RuntimeDirectoryMode = "0750";
+
+        StateDirectory = "headscale";
+        StateDirectoryMode = "0750";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        RestrictNamespaces = true;
+        RemoveIPC = true;
+        UMask = "0077";
+
+        CapabilityBoundingSet = capabilityBoundingSet;
+        AmbientCapabilities = capabilityBoundingSet;
+        NoNewPrivileges = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+        SystemCallFilter = ["@system-service" "~@privileged" "@chown"];
+        SystemCallArchitectures = "native";
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+      };
     };
   };
 
-  meta.maintainers = with maintainers; [ kradalby ];
+  meta.maintainers = with maintainers; [kradalby misterio77];
 }
diff --git a/nixos/modules/services/networking/rpcbind.nix b/nixos/modules/services/networking/rpcbind.nix
index aa04214debb..60e78dfec51 100644
--- a/nixos/modules/services/networking/rpcbind.nix
+++ b/nixos/modules/services/networking/rpcbind.nix
@@ -35,6 +35,16 @@ with lib;
 
     systemd.services.rpcbind = {
       wantedBy = [ "multi-user.target" ];
+      # rpcbind performs a check for /var/run/rpcbind.lock at startup
+      # and will crash if /var/run isn't present. In the stock NixOS
+      # var.conf tmpfiles configuration file, /var/run is symlinked to
+      # /run, so rpcbind can enter a race condition in which /var/run
+      # isn't symlinked yet but tries to interact with the path, so
+      # controlling the order explicitly here ensures that rpcbind can
+      # start successfully. The `wants` instead of `requires` should
+      # avoid creating a strict/brittle dependency.
+      wants = [ "systemd-tmpfiles-setup.service" ];
+      after = [ "systemd-tmpfiles-setup.service" ];
     };
 
     users.users.rpc = {
diff --git a/nixos/modules/services/networking/tox-node.nix b/nixos/modules/services/networking/tox-node.nix
index fa5b241f918..884fd55dae5 100644
--- a/nixos/modules/services/networking/tox-node.nix
+++ b/nixos/modules/services/networking/tox-node.nix
@@ -8,7 +8,7 @@ let
   homeDir = "/var/lib/tox-node";
 
   configFile = let
-    src = "${pkg.src}/dpkg/config.yml";
+    src = "${pkg.src}/tox_node/dpkg/config.yml";
     confJSON = pkgs.writeText "config.json" (
       builtins.toJSON {
         log-type = cfg.logType;
diff --git a/nixos/modules/services/security/aesmd.nix b/nixos/modules/services/security/aesmd.nix
index 7b0a46d6d02..8b3f010d7c4 100644
--- a/nixos/modules/services/security/aesmd.nix
+++ b/nixos/modules/services/security/aesmd.nix
@@ -25,6 +25,22 @@ in
       default = false;
       description = lib.mdDoc "Whether to build the PSW package in debug mode.";
     };
+    environment = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      description = mdDoc "Additional environment variables to pass to the AESM service.";
+      # Example environment variable for `sgx-azure-dcap-client` provider library
+      example = {
+        AZDCAP_COLLATERAL_VERSION = "v2";
+        AZDCAP_DEBUG_LOG_LEVEL = "INFO";
+      };
+    };
+    quoteProviderLibrary = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      example = literalExpression "pkgs.sgx-azure-dcap-client";
+      description = lib.mdDoc "Custom quote provider library to use.";
+    };
     settings = mkOption {
       description = lib.mdDoc "AESM configuration";
       default = { };
@@ -83,7 +99,6 @@ in
         storeAesmFolder = "${sgx-psw}/aesm";
         # Hardcoded path AESM_DATA_FOLDER in psw/ae/aesm_service/source/oal/linux/aesm_util.cpp
         aesmDataFolder = "/var/opt/aesmd/data";
-        aesmStateDirSystemd = "%S/aesmd";
       in
       {
         description = "Intel Architectural Enclave Service Manager";
@@ -98,8 +113,8 @@ in
         environment = {
           NAME = "aesm_service";
           AESM_PATH = storeAesmFolder;
-          LD_LIBRARY_PATH = storeAesmFolder;
-        };
+          LD_LIBRARY_PATH = makeLibraryPath [ cfg.quoteProviderLibrary ];
+        } // cfg.environment;
 
         # Make sure any of the SGX application enclave devices is available
         unitConfig.AssertPathExists = [
diff --git a/nixos/modules/services/web-apps/dolibarr.nix b/nixos/modules/services/web-apps/dolibarr.nix
index 5335c439329..f262099354d 100644
--- a/nixos/modules/services/web-apps/dolibarr.nix
+++ b/nixos/modules/services/web-apps/dolibarr.nix
@@ -1,11 +1,11 @@
 { config, pkgs, lib, ... }:
 let
-  inherit (lib) any boolToString concatStringsSep isBool isString literalExpression mapAttrsToList mkDefault mkEnableOption mkIf mkOption optionalAttrs types;
+  inherit (lib) any boolToString concatStringsSep isBool isString mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption optionalAttrs types;
 
   package = pkgs.dolibarr.override { inherit (cfg) stateDir; };
 
   cfg = config.services.dolibarr;
-  vhostCfg = config.services.nginx.virtualHosts."${cfg.domain}";
+  vhostCfg = lib.optionalAttr (cfg.nginx != null) config.services.nginx.virtualHosts."${cfg.domain}";
 
   mkConfigFile = filename: settings:
     let
@@ -38,7 +38,7 @@ let
     force_install_database = cfg.database.name;
     force_install_databaselogin = cfg.database.user;
 
-    force_install_mainforcehttps = vhostCfg.forceSSL;
+    force_install_mainforcehttps = vhostCfg.forceSSL or false;
     force_install_createuser = false;
     force_install_dolibarrlogin = null;
   } // optionalAttrs (cfg.database.passwordFile != null) {
@@ -183,7 +183,8 @@ in
   };
 
   # implementation
-  config = mkIf cfg.enable {
+  config = mkIf cfg.enable (mkMerge [
+    {
 
     assertions = [
       { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
@@ -214,7 +215,7 @@ in
 
       # Security settings
       dolibarr_main_prod = true;
-      dolibarr_main_force_https = vhostCfg.forceSSL;
+      dolibarr_main_force_https = vhostCfg.forceSSL or false;
       dolibarr_main_restrict_os_commands = "${pkgs.mariadb}/bin/mysqldump, ${pkgs.mariadb}/bin/mysql";
       dolibarr_nocsrfcheck = false;
       dolibarr_main_instance_unique_id = ''
@@ -314,7 +315,9 @@ in
     users.groups = optionalAttrs (cfg.group == "dolibarr") {
       dolibarr = { };
     };
-
-    users.users."${config.services.nginx.group}".extraGroups = [ cfg.group ];
-  };
+  }
+  (mkIf (cfg.nginx != null) {
+    users.users."${config.services.nginx.group}".extraGroups = mkIf (cfg.nginx != null) [ cfg.group ];
+  })
+]);
 }
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index 4dbcb09d2ae..7e418f2869c 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -161,6 +161,18 @@ in {
       description = lib.mdDoc "Configure nginx as a reverse proxy for peertube.";
     };
 
+    secrets = {
+      secretsFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/secrets/peertube";
+        description = lib.mdDoc ''
+          Secrets to run PeerTube.
+          Generate one using `openssl rand -hex 32`
+        '';
+      };
+    };
+
     database = {
       createLocally = lib.mkOption {
         type = lib.types.bool;
@@ -201,7 +213,7 @@ in {
       passwordFile = lib.mkOption {
         type = lib.types.nullOr lib.types.path;
         default = null;
-        example = "/run/keys/peertube/password-posgressql-db";
+        example = "/run/keys/peertube/password-postgresql";
         description = lib.mdDoc "Password for PostgreSQL database.";
       };
     };
@@ -282,6 +294,11 @@ in {
             prevent this.
           '';
       }
+      { assertion = cfg.secrets.secretsFile != null;
+          message = ''
+            <option>services.peertube.secrets.secretsFile</option> needs to be set.
+          '';
+      }
       { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null));
           message = ''
             <option>services.peertube.redis.createLocally</option> and redis network connection (<option>services.peertube.redis.host</option> or <option>services.peertube.redis.port</option>) enabled. Disable either of them.
@@ -349,6 +366,7 @@ in {
           captions = lib.mkDefault "/var/lib/peertube/storage/captions/";
           cache = lib.mkDefault "/var/lib/peertube/storage/cache/";
           plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/";
+          well_known = lib.mkDefault "/var/lib/peertube/storage/well_known/";
           client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/";
         };
         import = {
@@ -417,6 +435,10 @@ in {
         #!/bin/sh
         umask 077
         cat > /var/lib/peertube/config/local.yaml <<EOF
+        ${lib.optionalString (cfg.secrets.secretsFile != null) ''
+        secrets:
+          peertube: '$(cat ${cfg.secrets.secretsFile})'
+        ''}
         ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) ''
         database:
           password: '$(cat ${cfg.database.passwordFile})'
@@ -443,6 +465,7 @@ in {
         RestartSec = 20;
         TimeoutSec = 60;
         WorkingDirectory = cfg.package;
+        SyslogIdentifier = "peertube";
         # User and group
         User = cfg.user;
         Group = cfg.group;
@@ -548,9 +571,14 @@ in {
           '';
         };
 
+        locations."~ ^/plugins/[^/]+(/[^/]+)?/ws/" = {
+          tryFiles = "/dev/null @api_websocket";
+          priority = 1230;
+        };
+
         locations."@api_websocket" = {
           proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
-          priority = 1230;
+          priority = 1240;
 
           extraConfig = ''
             proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
@@ -581,7 +609,7 @@ in {
           '';
         };
 
-        locations."~ ^/lazy-static/(avatars|banners)/" = {
+        locations."^~ /lazy-static/avatars/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.avatars;
           priority = 1330;
@@ -599,6 +627,26 @@ in {
             add_header Cache-Control                    'public, max-age=7200';
 
             rewrite ^/lazy-static/avatars/(.*)$         /$1 break;
+          '';
+        };
+
+        locations."^~ /lazy-static/banners/" = {
+          tryFiles = "$uri @api";
+          root = cfg.settings.storage.avatars;
+          priority = 1340;
+          extraConfig = ''
+            if ($request_method = 'OPTIONS') {
+              ${nginxCommonHeaders}
+              add_header Access-Control-Max-Age         1728000;
+              add_header Cache-Control                  'no-cache';
+              add_header Content-Type                   'text/plain charset=UTF-8';
+              add_header Content-Length                 0;
+              return                                    204;
+            }
+
+            ${nginxCommonHeaders}
+            add_header Cache-Control                    'public, max-age=7200';
+
             rewrite ^/lazy-static/banners/(.*)$         /$1 break;
           '';
         };
@@ -606,7 +654,7 @@ in {
         locations."^~ /lazy-static/previews/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.previews;
-          priority = 1340;
+          priority = 1350;
           extraConfig = ''
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
@@ -624,10 +672,34 @@ in {
           '';
         };
 
+        locations."^~ /static/streaming-playlists/private/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1410;
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
+
+            proxy_limit_rate                            5M;
+          '';
+        };
+
+        locations."^~ /static/webseed/private/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1420;
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
+
+            proxy_limit_rate                            5M;
+          '';
+        };
+
         locations."^~ /static/thumbnails/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.thumbnails;
-          priority = 1350;
+          priority = 1430;
           extraConfig = ''
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
@@ -648,8 +720,14 @@ in {
         locations."^~ /static/redundancy/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.redundancy;
-          priority = 1360;
+          priority = 1440;
           extraConfig = ''
+            set $peertube_limit_rate                    800k;
+
+            if ($request_uri ~ -fragmented.mp4$) {
+              set $peertube_limit_rate                  5M;
+            }
+
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
               add_header Access-Control-Max-Age         1728000;
@@ -662,15 +740,14 @@ in {
 
               access_log                                off;
             }
+
             aio                                         threads;
             sendfile                                    on;
             sendfile_max_chunk                          1M;
 
+            limit_rate                                  $peertube_limit_rate;
             limit_rate_after                            5M;
 
-            set $peertube_limit_rate                    800k;
-            set $limit_rate                             $peertube_limit_rate;
-
             rewrite ^/static/redundancy/(.*)$           /$1 break;
           '';
         };
@@ -678,8 +755,14 @@ in {
         locations."^~ /static/streaming-playlists/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.streaming_playlists;
-          priority = 1370;
+          priority = 1450;
           extraConfig = ''
+            set $peertube_limit_rate                    800k;
+
+            if ($request_uri ~ -fragmented.mp4$) {
+              set $peertube_limit_rate                  5M;
+            }
+
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
               add_header Access-Control-Max-Age         1728000;
@@ -697,20 +780,24 @@ in {
             sendfile                                    on;
             sendfile_max_chunk                          1M;
 
+            limit_rate                                  $peertube_limit_rate;
             limit_rate_after                            5M;
 
-            set $peertube_limit_rate                    5M;
-            set $limit_rate                             $peertube_limit_rate;
-
             rewrite ^/static/streaming-playlists/(.*)$  /$1 break;
           '';
         };
 
-        locations."~ ^/static/webseed/" = {
+        locations."^~ /static/webseed/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.videos;
-          priority = 1380;
+          priority = 1460;
           extraConfig = ''
+            set $peertube_limit_rate                    800k;
+
+            if ($request_uri ~ -fragmented.mp4$) {
+              set $peertube_limit_rate                  5M;
+            }
+
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
               add_header Access-Control-Max-Age         1728000;
@@ -728,11 +815,9 @@ in {
             sendfile                                    on;
             sendfile_max_chunk                          1M;
 
+            limit_rate                                  $peertube_limit_rate;
             limit_rate_after                            5M;
 
-            set $peertube_limit_rate                    800k;
-            set $limit_rate                             $peertube_limit_rate;
-
             rewrite ^/static/webseed/(.*)$              /$1 break;
           '';
         };
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 0b0e0de21df..8377e8a76d5 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -241,7 +241,7 @@ let
 
   configPath = if cfg.enableReload
     then "/etc/nginx/nginx.conf"
-    else configFile;
+    else finalConfigFile;
 
   execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
 
@@ -393,6 +393,38 @@ let
   );
 
   mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
+
+  snakeOilCert = pkgs.runCommand "nginx-config-validate-cert" { nativeBuildInputs = [ pkgs.openssl.bin ]; } ''
+    mkdir $out
+    openssl genrsa -des3 -passout pass:xxxxx -out server.pass.key 2048
+    openssl rsa -passin pass:xxxxx -in server.pass.key -out $out/server.key
+    openssl req -new -key $out/server.key -out server.csr \
+    -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
+    openssl x509 -req -days 1 -in server.csr -signkey $out/server.key -out $out/server.crt
+  '';
+  validatedConfigFile = pkgs.runCommand "validated-nginx.conf" { nativeBuildInputs = [ cfg.package ]; } ''
+    # nginx absolutely wants to read the certificates even when told to only validate config, so let's provide fake certs
+    sed ${configFile} \
+    -e "s|ssl_certificate .*;|ssl_certificate ${snakeOilCert}/server.crt;|g" \
+    -e "s|ssl_trusted_certificate .*;|ssl_trusted_certificate ${snakeOilCert}/server.crt;|g" \
+    -e "s|ssl_certificate_key .*;|ssl_certificate_key ${snakeOilCert}/server.key;|g" \
+    > conf
+
+    LD_PRELOAD=${pkgs.libredirect}/lib/libredirect.so \
+      NIX_REDIRECTS="/etc/resolv.conf=/dev/null" \
+      nginx -t -c $(readlink -f ./conf) > out 2>&1 || true
+    if ! grep -q "syntax is ok" out; then
+      echo nginx config validation failed.
+      echo config was ${configFile}.
+      echo 'in case of false positive, set `services.nginx.validateConfig` to false.'
+      echo nginx output:
+      cat out
+      exit 1
+    fi
+    cp ${configFile} $out
+  '';
+
+  finalConfigFile = if cfg.validateConfig then validatedConfigFile else configFile;
 in
 
 {
@@ -491,6 +523,17 @@ in
         '';
       };
 
+      validateConfig = mkOption {
+        # FIXME: re-enable if we can make of the configurations work.
+        #default = pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform;
+        default = false;
+        defaultText = literalExpression "pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform";
+        type = types.bool;
+        description = lib.mdDoc ''
+          Validate the generated nginx config at build time. The check is not very robust and can be disabled in case of false positives. This is notably the case when cross-compiling or when using `include` with files outside of the store.
+        '';
+      };
+
       additionalModules = mkOption {
         default = [];
         type = types.listOf (types.attrsOf types.anything);
@@ -1029,7 +1072,7 @@ in
     };
 
     environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
-      source = configFile;
+      source = finalConfigFile;
     };
 
     # This service waits for all certificates to be available
@@ -1048,7 +1091,7 @@ in
       # certs are updated _after_ config has been reloaded.
       before = sslTargets;
       after = sslServices;
-      restartTriggers = optionals (cfg.enableReload) [ configFile ];
+      restartTriggers = optionals (cfg.enableReload) [ finalConfigFile ];
       # Block reloading if not all certs exist yet.
       # Happens when config changes add new vhosts/certs.
       unitConfig.ConditionPathExists = optionals (sslServices != []) (map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames);
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index aaad1de5f87..08c5625fc7d 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -105,7 +105,7 @@ in
       services.dbus.packages = with pkgs.cinnamon; [
         cinnamon-common
         cinnamon-screensaver
-        nemo
+        nemo-with-extensions
         xapp
       ];
       services.cinnamon.apps.enable = mkDefault true;
@@ -154,7 +154,7 @@ in
         polkit_gnome
 
         # packages
-        nemo
+        nemo-with-extensions
         cinnamon-control-center
         cinnamon-settings-daemon
         libgnomekbd
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 2ab24951ec6..9fcb408c287 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -585,6 +585,8 @@ in
       hardware.bluetooth.enable = true;
       hardware.pulseaudio.enable = true;
       networking.networkmanager.enable = true;
+      # Required for autorotate
+      hardware.sensor.iio.enable = lib.mkDefault true;
 
       # Recommendations can be found here:
       #  - https://invent.kde.org/plasma-mobile/plasma-phone-settings/-/tree/master/etc/xdg
diff --git a/nixos/modules/system/activation/bootspec.cue b/nixos/modules/system/activation/bootspec.cue
index 3fc9ca381df..9f857a1b1cd 100644
--- a/nixos/modules/system/activation/bootspec.cue
+++ b/nixos/modules/system/activation/bootspec.cue
@@ -1,4 +1,5 @@
 #V1: {
+	system:         string
 	init:           string
 	initrd?:        string
 	initrdSecrets?: string
diff --git a/nixos/modules/system/activation/bootspec.nix b/nixos/modules/system/activation/bootspec.nix
index da76bf9084a..61407ab6755 100644
--- a/nixos/modules/system/activation/bootspec.nix
+++ b/nixos/modules/system/activation/bootspec.nix
@@ -19,13 +19,15 @@ let
           (builtins.toJSON
           {
             v1 = {
+              system = config.boot.kernelPackages.stdenv.hostPlatform.system;
               kernel = "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
               kernelParams = config.boot.kernelParams;
-              initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
-              initrdSecrets = "${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets";
               label = "NixOS ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})";
 
               inherit (cfg) extensions;
+            } // lib.optionalAttrs config.boot.initrd.enable {
+              initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
+              initrdSecrets = "${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets";
             };
           });
 
@@ -54,7 +56,7 @@ let
           specialisationInjector =
             let
               specialisationLoader = (lib.mapAttrsToList
-                (childName: childToplevel: lib.escapeShellArgs [ "--slurpfile" childName "${childToplevel}/bootspec/${filename}" ])
+                (childName: childToplevel: lib.escapeShellArgs [ "--slurpfile" childName "${childToplevel}/${filename}" ])
                 children);
             in
             lib.escapeShellArgs [
@@ -66,7 +68,7 @@ let
         ''
           mkdir -p $out/bootspec
 
-          ${toplevelInjector} | ${specialisationInjector} > $out/bootspec/${filename}
+          ${toplevelInjector} | ${specialisationInjector} > $out/${filename}
         '';
 
       validator = pkgs.writeCueValidator ./bootspec.cue {
@@ -80,7 +82,7 @@ in
     enable = lib.mkEnableOption (lib.mdDoc "Enable generation of RFC-0125 bootspec in $system/bootspec, e.g. /run/current-system/bootspec");
 
     extensions = lib.mkOption {
-      type = lib.types.attrs;
+      type = lib.types.attrsOf lib.types.attrs; # <namespace>: { ...namespace-specific fields }
       default = { };
       description = lib.mdDoc ''
         User-defined data that extends the bootspec document.
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 0bb3628ceed..00b11471e1c 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -81,7 +81,7 @@ let
 
       ${optionalString (!config.boot.isContainer && config.boot.bootspec.enable) ''
         ${config.boot.bootspec.writer}
-        ${config.boot.bootspec.validator} "$out/bootspec/${config.boot.bootspec.filename}"
+        ${config.boot.bootspec.validator} "$out/${config.boot.bootspec.filename}"
       ''}
 
       ${config.system.extraSystemBuilderCmds}
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 28c76fb169f..95dcdfd7fbe 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -205,8 +205,9 @@ let
       # Copy ld manually since it isn't detected correctly
       cp -pv ${pkgs.stdenv.cc.libc.out}/lib/ld*.so.? $out/lib
 
-      # Copy all of the needed libraries
-      find $out/bin $out/lib -type f | while read BIN; do
+      # Copy all of the needed libraries in a consistent order so
+      # duplicates are resolved the same way.
+      find $out/bin $out/lib -type f | sort | while read BIN; do
         echo "Copying libs for executable $BIN"
         for LIB in $(${findLibs}/bin/find-libs $BIN); do
           TGT="$out/lib/$(basename $LIB)"
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 4b4f4cc801a..0f14f2b501c 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -503,6 +503,10 @@ in
           assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot;
           message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot";
         }
+        {
+          assertion = cfgZfs.allowHibernation -> !cfgZfs.forceImportRoot && !cfgZfs.forceImportAll;
+          message = "boot.zfs.allowHibernation while force importing is enabled will cause data corruption";
+        }
       ];
 
       boot = {
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index 118bf82cdd6..13bbb4471ea 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -109,6 +109,37 @@ in
       '';
     };
 
+    autoPrune = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to periodically prune Podman resources. If enabled, a
+          systemd timer will run `podman system prune -f`
+          as specified by the `dates` option.
+        '';
+      };
+
+      flags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--all" ];
+        description = lib.mdDoc ''
+          Any additional flags passed to {command}`podman system prune`.
+        '';
+      };
+
+      dates = mkOption {
+        default = "weekly";
+        type = types.str;
+        description = lib.mdDoc ''
+          Specification (in the format described by
+          {manpage}`systemd.time(7)`) of the time at
+          which the prune will occur.
+        '';
+      };
+    };
+
     package = lib.mkOption {
       type = types.package;
       default = podmanPackage;
@@ -151,6 +182,23 @@ in
         ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
       };
 
+      systemd.services.podman-prune = {
+        description = "Prune podman resources";
+
+        restartIfChanged = false;
+        unitConfig.X-StopOnRemoval = false;
+
+        serviceConfig.Type = "oneshot";
+
+        script = ''
+          ${cfg.package}/bin/podman system prune -f ${toString cfg.autoPrune.flags}
+        '';
+
+        startAt = lib.optional cfg.autoPrune.enable cfg.autoPrune.dates;
+        after = [ "podman.service" ];
+        requires = [ "podman.service" ];
+      };
+
       systemd.sockets.podman.wantedBy = [ "sockets.target" ];
       systemd.sockets.podman.socketConfig.SocketGroup = "podman";