From 6df9ec335e2eb59abb9130adb7e805e9090275b3 Mon Sep 17 00:00:00 2001 From: NAHO <90870942+trueNAHO@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:42:57 +0100 Subject: [PATCH 01/19] mdbook: 0.5.1 -> 0.5.2 --- pkgs/by-name/md/mdbook/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/md/mdbook/package.nix b/pkgs/by-name/md/mdbook/package.nix index 010aabc320d4..66c14abfb130 100644 --- a/pkgs/by-name/md/mdbook/package.nix +++ b/pkgs/by-name/md/mdbook/package.nix @@ -8,7 +8,7 @@ installShellFiles, }: let - version = "0.5.1"; + version = "0.5.2"; in rustPlatform.buildRustPackage rec { inherit version; @@ -18,10 +18,10 @@ rustPlatform.buildRustPackage rec { owner = "rust-lang"; repo = "mdBook"; tag = "v${version}"; - hash = "sha256-t7Qou3H6dlO97puWQGkPlyb0jjpGoYCrz041iZWWL/s="; + hash = "sha256-gyjD47ZR9o2lIxipzesyJ6mxb9J9W+WS77TNWhKHP6U="; }; - cargoHash = "sha256-bJr0u025syrP/LGgIbXD0mRvQrvnnOntiaAfr/9tQ90="; + cargoHash = "sha256-230KljOUSrDy8QCQki7jvJvdAsjVlUEjKDNVyTF4tWs="; nativeBuildInputs = [ installShellFiles ]; From 5da17f180194734828c9b19c1d570d2c65037aa7 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Tue, 13 Jan 2026 06:43:33 +0000 Subject: [PATCH 02/19] mdadm: switch to `fetchgit` for releases `4.5` was tagged a while ago, but did not have a release tarball in an usual place. --- pkgs/by-name/md/mdadm4/package.nix | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/md/mdadm4/package.nix b/pkgs/by-name/md/mdadm4/package.nix index c4e2ec5dbde1..d5cb54bc0766 100644 --- a/pkgs/by-name/md/mdadm4/package.nix +++ b/pkgs/by-name/md/mdadm4/package.nix @@ -3,6 +3,7 @@ stdenv, util-linux, coreutils, + fetchgit, fetchurl, groff, system-sendmail, @@ -16,9 +17,10 @@ stdenv.mkDerivation rec { pname = "mdadm"; version = "4.4"; - src = fetchurl { - url = "mirror://kernel/linux/utils/raid/mdadm/mdadm-${version}.tar.xz"; - sha256 = "sha256-m0iPNe0VPfmZJLX+Qe7TgOhRLejxihGGKMrN1oGx1XM="; + src = fetchgit { + url = "https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git"; + tag = "mdadm-${version}"; + hash = "sha256-jGmc8fkJM0V9J7V7tQPXSF/WD0kzyEAloBAwaAFenS0="; }; patches = [ From ad69c9b28c01a89d15f38c866cad7dbc3445d8ec Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Tue, 13 Jan 2026 09:03:41 +0100 Subject: [PATCH 03/19] nixos/networking-interfaces: stop installing wireless packages iw and wirelesstools (iwconfig, iwlist, etc.) are not required by networking.wireless and rarely used manually, so they should not be installed by default. --- nixos/doc/manual/release-notes/rl-2605.section.md | 2 ++ nixos/modules/tasks/network-interfaces.nix | 8 ++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 00d22c04cd1e..e887a559092c 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -46,6 +46,8 @@ - `services.tandoor-recipes` now uses a sub-directory for media files by default starting with `26.05`. Existing setups should move media files out of the data directory and adjust `services.tandoor-recipes.extraConfig.MEDIA_ROOT` accordingly. See [Migrating media files for pre 26.05 installations](#module-services-tandoor-recipes-migrating-media). +- The packages `iw` and `wirelesstools` (`iwconfig`, `iwlist`, etc.) are no longer installed implicitly if wireless networking has been enabled. + - `services.kubernetes.addons.dns.coredns` has been renamed to `services.kubernetes.addons.dns.corednsImage` and now expects a package instead of attrs. Now, by default, nixpkgs.coredns in conjunction with dockerTools.buildImage is used, instead of pulling the upstream container image from Docker Hub. If you want the old behavior, you can set: diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 2a6d00296303..933fbce6a6b3 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -1769,13 +1769,9 @@ in environment.corePackages = [ pkgs.host - pkgs.hostname-debian + pkgs.hostname pkgs.iproute2 - pkgs.iputils - ] - ++ optionals config.networking.wireless.enable [ - pkgs.wirelesstools # FIXME: obsolete? - pkgs.iw + pkgs.iputils # ping ] ++ bridgeStp; From 610e1e8d4275367158d9d0e9f82a99fd9a842bfd Mon Sep 17 00:00:00 2001 From: Rafael Irgolic Date: Thu, 15 Jan 2026 14:48:25 +0000 Subject: [PATCH 04/19] nixos-rebuild-ng: avoid copying closure when `target_host` matches `build_host` --- pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py index f30ccf90d1ea..d2c5ceace95c 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py @@ -224,7 +224,7 @@ def copy_closure( ) match (to_host, from_host): - case (None, None): + case (x, y) if x == y: return case (Remote(_) as host, None) | (None, Remote(_) as host): nix_copy_closure(host, to=bool(to_host)) From 75bba21f040ef2402e4a26efe9510f85eb10e258 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Fri, 16 Jan 2026 11:45:14 +0100 Subject: [PATCH 05/19] nixos/resolvconf: use config.systemd.package instead of pkgs.systemd --- nixos/modules/system/boot/resolved.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix index b86e28f81a0a..971ee7a41fe4 100644 --- a/nixos/modules/system/boot/resolved.nix +++ b/nixos/modules/system/boot/resolved.nix @@ -140,7 +140,7 @@ in # If networkmanager is enabled, ask it to interface with resolved. networking.networkmanager.dns = "systemd-resolved"; - networking.resolvconf.package = pkgs.systemd; + networking.resolvconf.package = config.systemd.package; nix.firewall.extraNftablesRules = [ "ip daddr { 127.0.0.53, 127.0.0.54 } udp dport 53 accept comment \"systemd-resolved listening IPs\"" From c68bd3a9445b63e39452dcb758f4d0a4195e0ffd Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Wed, 14 Jan 2026 21:37:18 +0100 Subject: [PATCH 06/19] nixos/activation: prefer local build FIX #479747 --- nixos/modules/system/activation/activatable-system.nix | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix index 6513706b2769..7d1028a79a60 100644 --- a/nixos/modules/system/activation/activatable-system.nix +++ b/nixos/modules/system/activation/activatable-system.nix @@ -49,20 +49,22 @@ in config = let activationScript = lib.getExe ( - pkgs.writeShellApplication { + (pkgs.writeShellApplication { name = "activate"; text = config.system.activationScripts.script; checkPhase = ""; bashOptions = [ ]; - } + }).overrideAttrs + { preferLocalBuild = true; } ); dryActivationScript = lib.getExe ( - pkgs.writeShellApplication { + (pkgs.writeShellApplication { name = "dry-activate"; text = config.system.dryActivationScript; checkPhase = ""; bashOptions = [ ]; - } + }).overrideAttrs + { preferLocalBuild = true; } ); in { From 6f3c7db3465013faaa4dfb6f9beb1b5853c06628 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Mon, 13 Feb 2023 17:49:31 +0200 Subject: [PATCH 07/19] linux-enable-ir-emitter: init at 6.1.2 --- .../li/linux-enable-ir-emitter/package.nix | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 pkgs/by-name/li/linux-enable-ir-emitter/package.nix diff --git a/pkgs/by-name/li/linux-enable-ir-emitter/package.nix b/pkgs/by-name/li/linux-enable-ir-emitter/package.nix new file mode 100644 index 000000000000..220faab6e30f --- /dev/null +++ b/pkgs/by-name/li/linux-enable-ir-emitter/package.nix @@ -0,0 +1,62 @@ +{ + lib, + stdenv, + makeWrapper, + fetchFromGitHub, + meson, + ninja, + pkg-config, + argparse, + gtk3, + python3, + spdlog, + usbutils, + yaml-cpp, +}: +stdenv.mkDerivation (finalAttrs: { + pname = "linux-enable-ir-emitter"; + version = "6.1.2"; + + src = fetchFromGitHub { + owner = "EmixamPP"; + repo = "linux-enable-ir-emitter"; + tag = finalAttrs.version; + hash = "sha256-wSmWebX4H3Hj8bbFoVMq3DY3i/nKkQaeu3mXX0o6IaY="; + }; + + nativeBuildInputs = [ + makeWrapper + meson + ninja + pkg-config + ]; + + buildInputs = [ + argparse + gtk3 + spdlog + usbutils + yaml-cpp + python3.pkgs.opencv4Full + ]; + + mesonFlags = lib.lists.flatten [ + (lib.attrsets.mapAttrsToList lib.strings.mesonOption { + config_dir = "/var/lib"; + localstatedir = "/var"; + }) + (lib.attrsets.mapAttrsToList lib.strings.mesonBool { + create_config_dir = false; + create_log_dir = false; + }) + ]; + + meta = { + description = "Provides support for infrared cameras that are not directly enabled out-of-the box"; + homepage = "https://github.com/EmixamPP/linux-enable-ir-emitter"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ fufexan ]; + mainProgram = "linux-enable-ir-emitter"; + platforms = lib.platforms.linux; + }; +}) From 7a3e9ef2a1754a597180b81742bbc196d5fa6527 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Mon, 13 Feb 2023 18:53:46 +0200 Subject: [PATCH 08/19] howdy: init at 3.0.0 --- pkgs/by-name/ho/howdy/package.nix | 166 ++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 pkgs/by-name/ho/howdy/package.nix diff --git a/pkgs/by-name/ho/howdy/package.nix b/pkgs/by-name/ho/howdy/package.nix new file mode 100644 index 000000000000..76736305daf9 --- /dev/null +++ b/pkgs/by-name/ho/howdy/package.nix @@ -0,0 +1,166 @@ +{ + stdenv, + lib, + copyDesktopItems, + fetchFromGitHub, + fetchpatch, + fetchurl, + bzip2, + gobject-introspection, + imagemagick, + meson, + ninja, + pkg-config, + wrapGAppsHook3, + makeDesktopItem, + makeWrapper, + fmt, + gettext, + gtk3, + inih, + libevdev, + pam, + python3, +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "howdy"; + version = "3.0.0"; + + src = fetchFromGitHub { + owner = "boltgolt"; + repo = "howdy"; + # The 3.0.0 release does not have a corresponding tag yet. + # The maintainer was asked to provide one here https://github.com/boltgolt/howdy/pull/1023#issuecomment-3722339500 + rev = "d3ab99382f88f043d15f15c1450ab69433892a1c"; + hash = "sha256-Xd/uScMnX1GMwLD5GYSbE2CwEtzrhwHocsv0ESKV8IM="; + }; + + patches = [ + # Allows specifying whether to install config file. Paired with the + # `install_config` meson option. Needed to disallow installing the config + # file in `/etc/howdy`, as it is not allowed by the Nix sandbox. A NixOS + # module creates `/etc/howdy` and the config file of course. + # PR sent upstream https://github.com/boltgolt/howdy/pull/1050 + (fetchpatch { + url = "https://github.com/boltgolt/howdy/commit/1f3b83e2db5a8dfd9c7c88706ecce033e154060a.patch"; + hash = "sha256-OIN8A4q0zjtMOMzZgBqrKy2qOD8BDPB+euG6zerFbCE="; + }) + + # Fix python path for howdy-gtk. Uses python from meson option instead of + # the system installation (which is not defined in this package). + # PR sent upstream https://github.com/boltgolt/howdy/pull/1049 + (fetchpatch { + url = "https://github.com/boltgolt/howdy/commit/b056724f84361dc6150554e7a806152af032c54b.patch"; + hash = "sha256-ZOb+QmWagKWtyXI0Xg00tnw8UP8uDWw7wb4Fwjy3VeE="; + }) + ]; + + mesonFlags = lib.concatLists [ + (lib.mapAttrsToList lib.mesonOption { + config_dir = "/etc/howdy"; + python_path = "${finalAttrs.finalPackage.pythonEnv}/bin/python"; + user_models_dir = "/var/lib/howdy/models"; + }) + (lib.mapAttrsToList lib.mesonBool { + install_config = false; + with_polkit = true; + }) + ]; + + nativeBuildInputs = [ + bzip2 + copyDesktopItems + gobject-introspection + imagemagick + meson + ninja + pkg-config + wrapGAppsHook3 + makeWrapper + ]; + + buildInputs = [ + fmt + gettext + gtk3 + inih + libevdev + pam + ]; + + desktopItems = [ + (makeDesktopItem { + name = "howdy"; + exec = "howdy-gtk"; + icon = "howdy"; + comment = "Howdy facial authentication"; + desktopName = "Howdy"; + genericName = "Facial authentication"; + categories = [ + "System" + "Security" + ]; + }) + ]; + + postInstall = '' + # install dlib data + rm -rf $out/share/dlib-data/* + ${lib.concatStrings ( + lib.mapAttrsToList (n: v: '' + bzip2 -dc ${v} > $out/share/dlib-data/${n}.dat + '') finalAttrs.finalPackage.passthru.dlibModels + )} + + for size in 16 24 32 48 64 128 256 512; do + mkdir -p $out/share/icons/hicolor/"$size"x"$size"/apps + magick -background none "$out/share/howdy-gtk/logo.png" -resize "$size"x"$size" $out/share/icons/hicolor/"$size"x"$size"/apps/howdy.png + done + + chmod +x $out/lib/howdy-gtk/init.py + ''; + + pythonEnv = python3.buildEnv.override { + extraLibs = lib.attrVals finalAttrs.finalPackage.passthru.pythonDeps python3.pkgs; + makeWrapperArgs = [ + "--set" + "OMP_NUM_THREADS" + "1" + ]; + }; + + passthru = { + pythonDeps = [ + "dlib" + "elevate" + "face-recognition" + "keyboard" + "opencv4Full" + "pycairo" + "pygobject3" + ]; + dlibModels = lib.mapAttrs ( + name: hash: + fetchurl { + name = "howdy-${name}.dat"; + url = "https://github.com/davisking/dlib-models/raw/daf943f7819a3dda8aec4276754ef918dc26491f/${name}.dat.bz2"; + inherit hash; + } + ) finalAttrs.finalPackage.passthru.dlibModelsHashes; + dlibModelsHashes = { + dlib_face_recognition_resnet_model_v1 = "sha256-q7H2EEHkNEZYVc6Bwr1UboMNKLy+2NJ/++W7QIsRVTo="; + mmod_human_face_detector = "sha256-256eQPCSwRjV6z5kOTWyFoOBcHk1WVFVQcVqK1DZ/IQ="; + shape_predictor_5_face_landmarks = "sha256-bnh7vr9cnv23k/bNHwIyMMRBMwZgXyTymfEoaflapHI="; + }; + }; + + meta = { + description = "Windows Hello™ style facial authentication for Linux"; + homepage = "https://github.com/boltgolt/howdy"; + license = lib.licenses.mit; + mainProgram = "howdy"; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ fufexan ]; + }; +}) From c29b9ef82a8d9d75970bf5037c7facb623b418f3 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Wed, 22 Oct 2025 22:53:40 +0300 Subject: [PATCH 09/19] nixos/howdy: init --- .../manual/release-notes/rl-2605.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/security/pam.nix | 50 +++++++ .../services/security/howdy/default.nix | 122 ++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 nixos/modules/services/security/howdy/default.nix diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 12e0580cc96d..7d6e7e34d2a9 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -36,6 +36,8 @@ - [Dawarich](https://dawarich.app/), a self-hostable location history tracker. Available as [services.dawarich](#opt-services.dawarich.enable). +- [Howdy](https://github.com/boltgolt/howdy), a Windows Hello™ style facial authentication program for Linux. + - [udp-over-tcp](https://github.com/mullvad/udp-over-tcp), a tunnel for proxying UDP traffic over a TCP stream. Available as `services.udp-over-tcp`. - [Komodo Periphery](https://github.com/moghtech/komodo), a multi-server Docker and Git deployment agent by Komodo. Available as [services.komodo-periphery](#opt-services.komodo-periphery.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index bec540d6f504..19be56b1241c 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1488,6 +1488,7 @@ ./services/security/hockeypuck.nix ./services/security/hologram-agent.nix ./services/security/hologram-server.nix + ./services/security/howdy ./services/security/infnoise.nix ./services/security/intune.nix ./services/security/jitterentropy-rngd.nix diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index 12fdf781e9ec..176a8e4c79eb 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -322,6 +322,28 @@ let ''; }; + howdy = { + enable = lib.mkOption { + default = config.security.pam.howdy.enable; + defaultText = lib.literalExpression "config.security.pam.howdy.enable"; + type = lib.types.bool; + description = '' + Whether to enable the Howdy PAM module. + + If set, users can be authenticated using Howdy, the Windows + Hello™-style facial authentication service. + ''; + }; + control = lib.mkOption { + default = config.security.pam.howdy.control; + defaultText = lib.literalExpression "config.security.pam.howdy.control"; + type = lib.types.str; + description = '' + This option sets the PAM "control" used for this module. + ''; + }; + }; + oathAuth = lib.mkOption { default = config.security.pam.oath.enable; defaultText = lib.literalExpression "config.security.pam.oath.enable"; @@ -951,6 +973,12 @@ let control = "sufficient"; modulePath = "${config.services.fprintd.package}/lib/security/pam_fprintd.so"; } + { + name = "howdy"; + enable = cfg.howdy.enable; + control = cfg.howdy.control; + modulePath = "${config.services.howdy.package}/lib/security/pam_howdy.so"; + } ] ++ # Modules in this block require having the password set in PAM_AUTHTOK. @@ -1797,6 +1825,28 @@ in }; }; + security.pam.howdy = { + enable = lib.mkOption { + default = config.services.howdy.enable; + defaultText = lib.literalExpression "config.services.howdy.enable"; + type = lib.types.bool; + description = '' + Whether to enable the Howdy PAM module. + + If set, users can be authenticated using Howdy, the Windows + Hello™-style facial authentication service. + ''; + }; + control = lib.mkOption { + default = config.services.howdy.control; + defaultText = lib.literalExpression "config.services.howdy.control"; + type = lib.types.str; + description = '' + This option sets the PAM "control" used for this module. + ''; + }; + }; + security.pam.krb5 = { enable = lib.mkOption { default = config.security.krb5.enable; diff --git a/nixos/modules/services/security/howdy/default.nix b/nixos/modules/services/security/howdy/default.nix new file mode 100644 index 000000000000..80ba52ac1965 --- /dev/null +++ b/nixos/modules/services/security/howdy/default.nix @@ -0,0 +1,122 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.howdy; + settingsType = pkgs.formats.ini { }; + + default_config = { + core = { + detection_notice = false; + timeout_notice = true; + no_confirmation = false; + suppress_unknown = false; + abort_if_ssh = true; + abort_if_lid_closed = true; + disabled = false; + use_cnn = false; + workaround = "off"; + }; + + video = { + certainty = 3.5; + timeout = 4; + device_path = "/dev/video2"; + warn_no_device = true; + max_height = 320; + frame_width = -1; + frame_height = -1; + dark_threshold = 60; + recording_plugin = "opencv"; + device_format = "v4l2"; + force_mjpeg = false; + exposure = -1; + device_fps = -1; + rotate = 0; + }; + + snapshots = { + save_failed = false; + save_successful = false; + }; + + rubberstamps = { + enabled = false; + stamp_rules = "nod 5s failsafe min_distance=12"; + }; + + debug = { + end_report = false; + verbose_stamps = false; + gtk_stdout = false; + }; + }; +in +{ + options = { + services.howdy = { + enable = lib.mkEnableOption "" // { + description = '' + Whether to enable Howdy and its PAM module for face recognition. See + `services.linux-enable-ir-emitter` for enabling the IR emitter support. + + ::: {.caution} + Howdy is not a safe alternative to unlocking with your password. It + can be fooled using a well-printed photo. + + Do **not** use it as the sole authentication method for your system. + ::: + + ::: {.note} + By default, the {option}`config.services.howdy.control` option is set + to `"required"`, meaning it will act as a second-factor authentication + in most services. To change this, set the option to `"sufficient"`. + ::: + ''; + }; + + package = lib.mkPackageOption pkgs "howdy" { }; + + control = lib.mkOption { + type = lib.types.str; + default = "required"; + description = '' + PAM control flag to use for Howdy. + + Sets the {option}`security.pam.howdy.control` option. + + Refer to {manpage}`pam.conf(5)` for options. + ''; + }; + + settings = lib.mkOption { + inherit (settingsType) type; + default = default_config; + description = '' + Howdy configuration file. Refer to + + for options. + ''; + }; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + environment.etc."howdy/config.ini".source = settingsType.generate "howdy-config.ini" cfg.settings; + assertions = [ + { + assertion = !(builtins.elem "v4l2loopback" config.boot.kernelModules); + message = "Adding 'v4l2loopback' to `boot.kernelModules` causes Howdy to no longer work. Consider adding 'v4l2loopback' to `boot.extraModulePackages` instead."; + } + ]; + }) + { + services.howdy.settings = lib.mapAttrsRecursive (name: lib.mkDefault) default_config; + } + ]; +} From beea11b99e0ee7e9891f2273743e1a07f84156a5 Mon Sep 17 00:00:00 2001 From: Mihai Fufezan Date: Wed, 22 Oct 2025 22:54:28 +0300 Subject: [PATCH 10/19] nixos/linux-enable-ir-emitter: init --- .../manual/release-notes/rl-2605.section.md | 2 + nixos/modules/module-list.nix | 1 + .../services/misc/linux-enable-ir-emitter.nix | 71 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 nixos/modules/services/misc/linux-enable-ir-emitter.nix diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 7d6e7e34d2a9..2e40f225818d 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -38,6 +38,8 @@ - [Howdy](https://github.com/boltgolt/howdy), a Windows Hello™ style facial authentication program for Linux. +- [linux-enable-ir-emitter](https://github.com/EmixamPP/linux-enable-ir-emitter), a tool used to set up IR cameras, used with Howdy. + - [udp-over-tcp](https://github.com/mullvad/udp-over-tcp), a tunnel for proxying UDP traffic over a TCP stream. Available as `services.udp-over-tcp`. - [Komodo Periphery](https://github.com/moghtech/komodo), a multi-server Docker and Git deployment agent by Komodo. Available as [services.komodo-periphery](#opt-services.komodo-periphery.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 19be56b1241c..8422d5f308c2 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -878,6 +878,7 @@ ./services/misc/languagetool.nix ./services/misc/leaps.nix ./services/misc/lifecycled.nix + ./services/misc/linux-enable-ir-emitter.nix ./services/misc/litellm.nix ./services/misc/llama-cpp.nix ./services/misc/local-content-share.nix diff --git a/nixos/modules/services/misc/linux-enable-ir-emitter.nix b/nixos/modules/services/misc/linux-enable-ir-emitter.nix new file mode 100644 index 000000000000..66d10fc87b35 --- /dev/null +++ b/nixos/modules/services/misc/linux-enable-ir-emitter.nix @@ -0,0 +1,71 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.linux-enable-ir-emitter; +in +{ + options = { + services.linux-enable-ir-emitter = { + enable = lib.mkEnableOption "" // { + description = '' + Whether to enable IR emitter hardware. Designed to be used with the + Howdy facial authentication. After enabling the service, configure + the emitter with `sudo linux-enable-ir-emitter configure`. + ''; + }; + + package = lib.mkPackageOption pkgs "linux-enable-ir-emitter" { } // { + description = '' + Package to use for the Linux Enable IR Emitter service. + ''; + }; + + device = lib.mkOption { + type = lib.types.str; + default = "video2"; + description = '' + IR camera device to depend on. For example, for `/dev/video2` + the value would be `video2`. Find this with the command + {command}`realpath /dev/v4l/by-path/`. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + # https://github.com/EmixamPP/linux-enable-ir-emitter/blob/7e3a6527ef2efccabaeefc5a93c792628325a8db/sources/systemd/linux-enable-ir-emitter.service + systemd.services.linux-enable-ir-emitter = + let + targets = [ + "suspend.target" + "sleep.target" + "hybrid-sleep.target" + "hibernate.target" + "suspend-then-hibernate.target" + ]; + in + { + description = "Enable the infrared emitter"; + # Added to match + # https://github.com/EmixamPP/linux-enable-ir-emitter/blob/6.1.2/boot_service/systemd/linux-enable-ir-emitter.service + # Prevents the program fail to detect the IR camera until a service + # restart. + preStart = '' + ${pkgs.kmod}/bin/modprobe uvcvideo + sleep 1 + ''; + script = "${lib.getExe cfg.package} --verbose run"; + serviceConfig.StateDirectory = "linux-enable-ir-emitter"; + serviceConfig.LogsDirectory = "linux-enable-ir-emitter"; + + wantedBy = targets ++ [ "multi-user.target" ]; + after = targets ++ [ "dev-${cfg.device}.device" ]; + }; + }; +} From 1baa98d054dada1745ba7e4007cc0d278c97bc58 Mon Sep 17 00:00:00 2001 From: Doron Behar Date: Wed, 21 Jan 2026 12:39:49 +0200 Subject: [PATCH 11/19] nano: add doc & man outputs --- pkgs/by-name/na/nano/package.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/by-name/na/nano/package.nix b/pkgs/by-name/na/nano/package.nix index a0a303a0d3d7..4273405a1456 100644 --- a/pkgs/by-name/na/nano/package.nix +++ b/pkgs/by-name/na/nano/package.nix @@ -43,7 +43,9 @@ stdenv.mkDerivation rec { outputs = [ "out" + "doc" "info" + "man" ]; configureFlags = [ From 5d4365c7a52756cb4776bc894f7c195818bd89a8 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Wed, 21 Jan 2026 16:09:30 +0100 Subject: [PATCH 12/19] linux/common-config: remove obsolete hack Tested that 6.18 and 6.19 now build without this. --- pkgs/os-specific/linux/kernel/common-config.nix | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkgs/os-specific/linux/kernel/common-config.nix b/pkgs/os-specific/linux/kernel/common-config.nix index 6c071d109a41..cfbe9dac4eb4 100644 --- a/pkgs/os-specific/linux/kernel/common-config.nix +++ b/pkgs/os-specific/linux/kernel/common-config.nix @@ -563,10 +563,6 @@ let # Enable CEC over DisplayPort DRM_DP_CEC = whenOlder "6.10" yes; DRM_DISPLAY_DP_AUX_CEC = whenAtLeast "6.10" yes; - - # Required for Nova - # FIXME: remove after https://gitlab.freedesktop.org/drm/rust/kernel/-/commit/3d3352e73a55a4ccf110f8b3419bbe2fbfd8a030 lands - RUST_FW_LOADER_ABSTRACTIONS = lib.mkIf withRust (whenAtLeast "6.12" yes); } // lib.optionalAttrs From 9bac056a72c7f3dfbd4ed7c13bf17cb239bb735a Mon Sep 17 00:00:00 2001 From: WilliButz Date: Tue, 16 Dec 2025 12:04:04 +0100 Subject: [PATCH 13/19] nixos/systemd: expose factory-reset units --- nixos/modules/system/boot/systemd.nix | 7 +++++++ nixos/modules/system/boot/systemd/initrd.nix | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 7fb5c2259062..7a606afcdd9d 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -213,6 +213,12 @@ let # Capsule support "capsule@.service" "capsule.slice" + + # Factory reset + "factory-reset.target" + "systemd-factory-reset-request.service" + "systemd-factory-reset-reboot.service" + "systemd-tpm2-clear.service" ] ++ cfg.additionalUpstreamSystemUnits; @@ -222,6 +228,7 @@ let "local-fs.target.wants" "multi-user.target.wants" "timers.target.wants" + "factory-reset.target.wants" ]; proxy_env = config.networking.proxy.envVars; diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index 53a73641f245..0c20432e58f4 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -65,6 +65,8 @@ let "syslog.socket" "systemd-ask-password-console.path" "systemd-ask-password-console.service" + "systemd-factory-reset-complete.service" + "factory-reset-now.target" "systemd-fsck@.service" "systemd-halt.service" "systemd-hibernate-resume.service" @@ -555,6 +557,7 @@ in "${cfg.package}/lib/systemd/systemd-sysctl" "${cfg.package}/lib/systemd/systemd-bsod" "${cfg.package}/lib/systemd/systemd-sysroot-fstab-check" + "${cfg.package}/lib/systemd/systemd-factory-reset" # generators "${cfg.package}/lib/systemd/system-generators/systemd-debug-generator" @@ -562,6 +565,7 @@ in "${cfg.package}/lib/systemd/system-generators/systemd-gpt-auto-generator" "${cfg.package}/lib/systemd/system-generators/systemd-hibernate-resume-generator" "${cfg.package}/lib/systemd/system-generators/systemd-run-generator" + "${cfg.package}/lib/systemd/system-generators/systemd-factory-reset-generator" # utilities needed by systemd "${cfg.package.util-linux}/bin/mount" From fc1a08753174c4c5e3ea0d8b7b470e3ca2e95f0a Mon Sep 17 00:00:00 2001 From: WilliButz Date: Mon, 19 Jan 2026 17:48:34 +0100 Subject: [PATCH 14/19] nixos/tests/systemd-repart: add test for factory reset --- nixos/tests/systemd-repart.nix | 102 +++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/nixos/tests/systemd-repart.nix b/nixos/tests/systemd-repart.nix index 96a33633dc46..01faf69d6a51 100644 --- a/nixos/tests/systemd-repart.nix +++ b/nixos/tests/systemd-repart.nix @@ -277,4 +277,106 @@ in assert "Adding new partition 2 to partition table." in systemd_repart_logs ''; }; + + factory-reset = makeTest { + name = "systemd-repart-factory-reset"; + meta.maintainers = with maintainers; [ willibutz ]; + + nodes.machine = + { pkgs, lib, ... }: + { + imports = [ common ]; + + virtualisation = { + useEFIBoot = true; + tpm.enable = true; + efi.OVMF = pkgs.OVMFFull; + useDefaultFilesystems = false; + fileSystems = { + "/state" = { + device = "/dev/mapper/state"; + fsType = "ext4"; + }; + }; + }; + + boot = { + loader.systemd-boot.enable = true; + + initrd = { + systemd = { + enable = true; + # avoids reaching cryptsetup.target before recreation of the + # "state" volume completed, during the factory reset + services.systemd-repart.before = [ + "systemd-cryptsetup@state.service" + ]; + repart = { + enable = true; + extraArgs = [ + "--tpm2-pcrs=platform-code" + ]; + }; + }; + luks.devices = lib.mkVMOverride { + state = { + device = "/dev/disk/by-partlabel/state"; + crypttabExtraOpts = [ "tpm2-device=auto" ]; + }; + }; + }; + }; + + systemd.repart.partitions = { + "10-esp".Type = "esp"; + "20-root".Type = "linux-generic"; + "30-state" = { + Type = "linux-generic"; + Label = "state"; + Format = "ext4"; + Encrypt = "tpm2"; + SizeMinBytes = "64M"; + SizeMaxBytes = "64M"; + FactoryReset = true; + }; + }; + + # doesn't actually reboot through the service because otherwise the test + # instrumentation becomes very unreliable, instead uses machine.reboot() + systemd.services.systemd-factory-reset-reboot.enable = false; + }; + + testScript = + { nodes, ... }: + # python + '' + ${useDiskImage { + inherit (nodes) machine; + sizeDiff = "+64M"; + }} + + machine.start(allow_reboot=True) + machine.wait_for_unit("default.target") + + first_uuid = machine.succeed("blkid -s UUID -o value /dev/disk/by-label/state") + + machine.succeed("mountpoint /state") + machine.succeed("touch /state/foo") + + with subtest("factory reset requested through target"): + machine.systemctl("start factory-reset.target") + + # reboot manually to keep control over test vm + machine.reboot() + machine.wait_for_unit("default.target") + + with subtest("state partition recreated and empty after reset"): + second_uuid = machine.succeed("blkid -s UUID -o value /dev/disk/by-label/state") + t.assertNotEqual(first_uuid, second_uuid) + + machine.succeed("mountpoint /state") + machine.fail("test -e /state/foo") + ''; + }; + } From 978a08c88fbd7448cb0385f0bc5732ad4c00dea7 Mon Sep 17 00:00:00 2001 From: zimbatm Date: Mon, 5 Jan 2026 18:45:15 +0100 Subject: [PATCH 15/19] nixos-rebuild-ng: allow deploying closures Tools that want to deploy NixOS all re-implement the same core logic available in `nixos-rebuild-ng` (bin/switch-to-configuration + nix-env -p) because they want to be able to deploy specific system closures. By adding a `--store-path` flag, tools can now directly invoke nixos-rebuild-ng instead. Bringing the interface to a higher level allows us to incorporate ideas such as health checks and auto-rollbacks that are harder to achieve on the lower layer. --- nixos/tests/all-tests.nix | 3 + nixos/tests/nixos-rebuild-store-path.nix | 101 ++++++++++ .../ni/nixos-rebuild-ng/nixos-rebuild.8.scd | 16 +- pkgs/by-name/ni/nixos-rebuild-ng/package.nix | 1 + .../src/nixos_rebuild/__init__.py | 23 ++- .../src/nixos_rebuild/services.py | 9 +- .../nixos-rebuild-ng/src/tests/test_main.py | 184 ++++++++++++++++++ 7 files changed, 334 insertions(+), 3 deletions(-) create mode 100644 nixos/tests/nixos-rebuild-store-path.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 6cd4ac9a4ecb..39542f1e683a 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1107,6 +1107,9 @@ in nixos-rebuild-specialisations = runTestOn [ "x86_64-linux" ] { imports = [ ./nixos-rebuild-specialisations.nix ]; }; + nixos-rebuild-store-path = runTestOn [ "x86_64-linux" ] { + imports = [ ./nixos-rebuild-store-path.nix ]; + }; nixos-rebuild-target-host = runTest { imports = [ ./nixos-rebuild-target-host.nix ]; }; diff --git a/nixos/tests/nixos-rebuild-store-path.nix b/nixos/tests/nixos-rebuild-store-path.nix new file mode 100644 index 000000000000..c451ff865744 --- /dev/null +++ b/nixos/tests/nixos-rebuild-store-path.nix @@ -0,0 +1,101 @@ +{ hostPkgs, ... }: +{ + name = "nixos-rebuild-store-path"; + + # TODO: remove overlay from nixos/modules/profiles/installation-device.nix + # make it a _small package instead, then remove pkgsReadOnly = false;. + node.pkgsReadOnly = false; + + nodes = { + machine = + { lib, pkgs, ... }: + { + imports = [ + ../modules/profiles/installation-device.nix + ../modules/profiles/base.nix + ]; + + nix.settings = { + substituters = lib.mkForce [ ]; + hashed-mirrors = null; + connect-timeout = 1; + }; + + system.includeBuildDependencies = true; + + system.extraDependencies = [ + # Not part of the initial build apparently? + pkgs.grub2 + ]; + + system.switch.enable = true; + + virtualisation = { + cores = 2; + memorySize = 4096; + }; + }; + }; + + testScript = + let + configFile = + hostname: + hostPkgs.writeText "configuration.nix" # nix + '' + { lib, pkgs, ... }: { + imports = [ + ./hardware-configuration.nix + + ]; + + boot.loader.grub = { + enable = true; + device = "/dev/vda"; + forceInstall = true; + }; + + documentation.enable = false; + + networking.hostName = "${hostname}"; + } + ''; + + in + # python + '' + machine.start() + machine.succeed("udevadm settle") + machine.wait_for_unit("multi-user.target") + + machine.succeed("nixos-generate-config") + + with subtest("Build configuration without switching"): + machine.copy_from_host( + "${configFile "store-path-test"}", + "/etc/nixos/configuration.nix", + ) + store_path = machine.succeed("nix-build '' -A system --no-out-link").strip() + machine.succeed(f"test -f {store_path}/nixos-version") + + with subtest("Switch using --store-path"): + machine.succeed(f"nixos-rebuild switch --store-path {store_path}") + hostname = machine.succeed("cat /etc/hostname").strip() + assert hostname == "store-path-test", f"Expected hostname 'store-path-test', got '{hostname}'" + + with subtest("Test using --store-path"): + machine.copy_from_host( + "${configFile "store-path-test-2"}", + "/etc/nixos/configuration.nix", + ) + store_path_2 = machine.succeed("nix-build '' -A system --no-out-link").strip() + machine.succeed(f"nixos-rebuild test --store-path {store_path_2}") + hostname = machine.succeed("cat /etc/hostname").strip() + assert hostname == "store-path-test-2", f"Expected hostname 'store-path-test-2', got '{hostname}'" + + with subtest("Ensure --store-path rejects invalid combinations"): + machine.fail(f"nixos-rebuild switch --store-path {store_path} --rollback") + machine.fail(f"nixos-rebuild switch --store-path {store_path} --flake .") + machine.fail(f"nixos-rebuild build --store-path {store_path}") + ''; +} diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd b/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd index 3e04426ba66a..bc7f09e036d1 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd +++ b/pkgs/by-name/ni/nixos-rebuild-ng/nixos-rebuild.8.scd @@ -21,7 +21,7 @@ nixos-rebuild - reconfigure a NixOS machine _nixos-rebuild_ \[--verbose] [--quiet] [--max-jobs MAX_JOBS] [--cores CORES] [--log-format LOG_FORMAT] [--keep-going] [--keep-failed] [--fallback] [--repair] [--option OPTION OPTION] [--builders BUILDERS] [--include INCLUDE]++ \[--print-build-logs] [--show-trace] [--accept-flake-config] [--refresh] [--impure] [--offline] [--no-net] [--recreate-lock-file] [--no-update-lock-file] [--no-write-lock-file] [--no-registries] [--commit-lock-file]++ \[--update-input UPDATE_INPUT] [--override-input OVERRIDE_INPUT OVERRIDE_INPUT] [--no-build-output] [--use-substitutes] [--help] [--debug] [--file FILE] [--attr ATTR] [--flake [FLAKE]] [--no-flake] [--install-bootloader]++ - \[--profile-name PROFILE_NAME] [--specialisation SPECIALISATION] [--rollback] [--upgrade] [--upgrade-all] [--json] [--ask-sudo-password] [--sudo] [--no-reexec]++ + \[--profile-name PROFILE_NAME] [--specialisation SPECIALISATION] [--rollback] [--store-path STORE_PATH] [--upgrade] [--upgrade-all] [--json] [--ask-sudo-password] [--sudo] [--no-reexec]++ \[--build-host BUILD_HOST] [--target-host TARGET_HOST] [--no-build-nix] [--image-variant IMAGE_VARIANT]++ \[{switch,boot,test,build,edit,repl,dry-build,dry-run,dry-activate,build-image,build-vm,build-vm-with-bootloader,list-generations}] @@ -182,6 +182,20 @@ It must be one of the following: (The previous configuration is defined as the one before the “current” generation of the Nix profile _/nix/var/nix/profiles/system_.) +*--store-path* _path_ + Use a pre-built NixOS system store path at _path_ instead of evaluating + and building from the configuration. This skips the evaluation and build + phases entirely. The path must be a valid NixOS system closure + (containing _nixos-version_ and _bin/switch-to-configuration_). + + This is useful for deploying closures that were built elsewhere, such as + in CI systems or on remote build machines. + + Can only be used with *switch*, *boot*, *test*, and *dry-activate* + actions. Mutually exclusive with *--rollback*, *--flake*, *--file*, and + *--attr*. The *--build-host* option is ignored when *--store-path* is + specified. + *--builders* _builder-spec_ Allow ad-hoc remote builders for building the new system. This requires the user executing *nixos-rebuild* (usually root) to be configured as a diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/package.nix b/pkgs/by-name/ni/nixos-rebuild-ng/package.nix index b62492b58b66..e08e5cca9c65 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/package.nix +++ b/pkgs/by-name/ni/nixos-rebuild-ng/package.nix @@ -98,6 +98,7 @@ python3Packages.buildPythonApplication rec { # FIXME: this test is disabled since it times out in @ofborg # nixos-rebuild-install-bootloader nixos-rebuild-specialisations + nixos-rebuild-store-path nixos-rebuild-target-host ; repl = callPackage ./tests/repl.nix { }; diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py index e4fa5ef75a43..bedfdba11536 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py @@ -136,6 +136,11 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa action="store_true", help="Roll back to the previous configuration", ) + main_parser.add_argument( + "--store-path", + metavar="PATH", + help="Use a pre-built NixOS system store path instead of building", + ) main_parser.add_argument( "--upgrade", action="store_true", @@ -269,6 +274,22 @@ def parse_args( if args.flake and (args.file or args.attr): parser.error("--flake cannot be used with --file or --attr") + if args.store_path: + if args.rollback: + parser.error("--store-path and --rollback are mutually exclusive") + if args.flake or args.file or args.attr: + parser.error("--store-path cannot be used with --flake, --file, or --attr") + if args.action not in ( + Action.SWITCH.value, + Action.BOOT.value, + Action.TEST.value, + Action.DRY_ACTIVATE.value, + ): + parser.error(f"--store-path cannot be used with '{args.action}'") + if args.flake is None: + # Disable flake auto-detection since we're using a pre-built store path + args.flake = False + return args, grouped_nix_args @@ -297,7 +318,7 @@ def execute(argv: list[str]) -> None: build_attr = BuildAttr.from_arg(args.attr, args.file) flake = Flake.from_arg(args.flake, target_host) - if can_run and not flake: + if can_run and not flake and not args.store_path: services.write_version_suffix(grouped_nix_args) match action: diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py index f1902bd30fb8..5d3a74ea821e 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/services.py @@ -290,7 +290,14 @@ def build_and_activate_system( grouped_nix_args=grouped_nix_args, ) - if args.rollback: + if args.store_path: + path_to_config = Path(args.store_path) + nix.copy_closure( + path_to_config, + to_host=target_host, + copy_flags=grouped_nix_args.copy_flags, + ) + elif args.rollback: path_to_config = _rollback_system( action=action, args=args, diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py index cb7c87ad74f3..cd8716c7d024 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py @@ -34,6 +34,30 @@ def test_parse_args() -> None: nr.parse_args(["nixos-rebuild", "edit", "--attr", "attr"]) assert e.value.code == 2 + # --store-path validation tests + with pytest.raises(SystemExit) as e: + nr.parse_args( + ["nixos-rebuild", "switch", "--store-path", "/nix/store/foo", "--rollback"] + ) + assert e.value.code == 2 + + with pytest.raises(SystemExit) as e: + nr.parse_args( + ["nixos-rebuild", "switch", "--store-path", "/nix/store/foo", "--flake"] + ) + assert e.value.code == 2 + + with pytest.raises(SystemExit) as e: + nr.parse_args(["nixos-rebuild", "build", "--store-path", "/nix/store/foo"]) + assert e.value.code == 2 + + # --store-path should disable flake auto-detection + r_store_path, _ = nr.parse_args( + ["nixos-rebuild", "switch", "--store-path", "/nix/store/foo"] + ) + assert r_store_path.flake is False + assert r_store_path.store_path == "/nix/store/foo" + r1, g1 = nr.parse_args( [ "nixos-rebuild", @@ -1214,3 +1238,163 @@ def test_execute_test_rollback( ), ] ) + + +@patch.dict( + os.environ, + {"NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM": "1"}, + clear=True, +) +@patch("subprocess.run", autospec=True) +def test_execute_switch_store_path(mock_run: Mock, tmp_path: Path) -> None: + config_path = tmp_path / "test-system" + config_path.mkdir() + + mock_run.return_value = CompletedProcess([], 0) + + nr.execute( + [ + "nixos-rebuild", + "switch", + "--store-path", + str(config_path), + "--no-reexec", + ] + ) + + # --store-path skips build and write_version_suffix, so only activation calls + assert mock_run.call_count == 3 + mock_run.assert_has_calls( + [ + call( + [ + "nix-env", + "-p", + Path("/nix/var/nix/profiles/system"), + "--set", + config_path, + ], + check=True, + **DEFAULT_RUN_KWARGS, + ), + call( + ["test", "-d", "/run/systemd/system"], + check=False, + **DEFAULT_RUN_KWARGS, + ), + call( + [ + *nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX, + config_path / "bin/switch-to-configuration", + "switch", + ], + check=True, + **( + DEFAULT_RUN_KWARGS + | { + "env": { + "NIXOS_INSTALL_BOOTLOADER": "0", + "NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM": "1", + } + } + ), + ), + ] + ) + + +@patch.dict(os.environ, {}, clear=True) +@patch("subprocess.run", autospec=True) +@patch(get_qualified_name(nr.services.cleanup_ssh), autospec=True) +def test_execute_switch_store_path_target_host( + mock_cleanup_ssh: Mock, + mock_run: Mock, + tmp_path: Path, +) -> None: + config_path = tmp_path / "test-system" + config_path.mkdir() + + mock_run.return_value = CompletedProcess([], 0) + + nr.execute( + [ + "nixos-rebuild", + "switch", + "--store-path", + str(config_path), + "--target-host", + "user@remote-host", + "--sudo", + "--no-reexec", + ] + ) + + # --store-path skips build and write_version_suffix, so only copy/activation calls + assert mock_run.call_count == 5 + mock_run.assert_has_calls( + [ + call( + ["nix-copy-closure", "--to", "user@remote-host", config_path], + check=True, + **DEFAULT_RUN_KWARGS, + ), + call( + [ + "ssh", + *nr.process.SSH_DEFAULT_OPTS, + "user@remote-host", + "--", + "test", + "-f", + str(config_path / "nixos-version"), + ], + check=False, + **DEFAULT_RUN_KWARGS, + ), + call( + [ + "ssh", + *nr.process.SSH_DEFAULT_OPTS, + "user@remote-host", + "--", + "sudo", + "nix-env", + "-p", + "/nix/var/nix/profiles/system", + "--set", + str(config_path), + ], + check=True, + **DEFAULT_RUN_KWARGS, + ), + call( + [ + "ssh", + *nr.process.SSH_DEFAULT_OPTS, + "user@remote-host", + "--", + "test", + "-d", + "/run/systemd/system", + ], + check=False, + **DEFAULT_RUN_KWARGS, + ), + call( + [ + "ssh", + *nr.process.SSH_DEFAULT_OPTS, + "user@remote-host", + "--", + "sudo", + "env", + "NIXOS_INSTALL_BOOTLOADER=0", + *nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX, + str(config_path / "bin/switch-to-configuration"), + "switch", + ], + check=True, + **DEFAULT_RUN_KWARGS, + ), + ] + ) From c2e94fb472633dc95c2a5ead901100f7a51e0278 Mon Sep 17 00:00:00 2001 From: Doron Behar Date: Thu, 22 Jan 2026 16:12:38 +0200 Subject: [PATCH 16/19] btrfs-progs: split dev, man & lib outputs --- pkgs/by-name/bt/btrfs-progs/package.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkgs/by-name/bt/btrfs-progs/package.nix b/pkgs/by-name/bt/btrfs-progs/package.nix index f521f193042a..dd7562919915 100644 --- a/pkgs/by-name/bt/btrfs-progs/package.nix +++ b/pkgs/by-name/bt/btrfs-progs/package.nix @@ -79,6 +79,13 @@ stdenv.mkDerivation rec { makeFlags = [ "udevruledir=$(out)/lib/udev/rules.d" ]; + outputs = [ + "out" + "dev" + "man" + "lib" + ]; + enableParallelBuilding = true; doInstallCheck = true; From d968dd7b9cddc6597a20c357516171fc750cf35c Mon Sep 17 00:00:00 2001 From: Jost Alemann Date: Thu, 22 Jan 2026 23:44:53 +0100 Subject: [PATCH 17/19] ruff: 0.14.13 -> 0.14.14 Changelog: https://github.com/astral-sh/ruff/releases/tag/0.14.14 Diff: https://github.com/astral-sh/ruff/compare/0.14.13...0.14.14 --- pkgs/by-name/ru/ruff/package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/by-name/ru/ruff/package.nix b/pkgs/by-name/ru/ruff/package.nix index 921b894cb3ee..788cbdd809ab 100644 --- a/pkgs/by-name/ru/ruff/package.nix +++ b/pkgs/by-name/ru/ruff/package.nix @@ -16,18 +16,18 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "ruff"; - version = "0.14.13"; + version = "0.14.14"; src = fetchFromGitHub { owner = "astral-sh"; repo = "ruff"; tag = finalAttrs.version; - hash = "sha256-ComgiY6fvM2f3Ul8kTgykyktvxEHL85X5C1Tudi5ZB4="; + hash = "sha256-h6XYWK6NxelLCfqG0geiAj3XbcqzbeFKeFMMDsy8fm8="; }; cargoBuildFlags = [ "--package=ruff" ]; - cargoHash = "sha256-Vd9y9VQF7d62tjHJ0ikWlvV7+KDs4q61ld9yoJWaqpY="; + cargoHash = "sha256-H5ZaBDV0YTdExu42Dt1Bq379vJ3FtddKtdLkMW+qC78="; nativeBuildInputs = [ installShellFiles ]; From df83b15f96b8a680b732ecfb0e3ff0ca57109ba9 Mon Sep 17 00:00:00 2001 From: zowoq <59103226+zowoq@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:35:58 +0000 Subject: [PATCH 18/19] linux_6_18: 6.18.6 -> 6.18.7 --- pkgs/os-specific/linux/kernel/kernels-org.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index 5cb786e4c85b..1e462e03278c 100644 --- a/pkgs/os-specific/linux/kernel/kernels-org.json +++ b/pkgs/os-specific/linux/kernel/kernels-org.json @@ -30,8 +30,8 @@ "lts": true }, "6.18": { - "version": "6.18.6", - "hash": "sha256:06x3z649mzwwkb1hvsy0yh7j5jk9qrnwqcmwy7dx8s1ggccrf927", + "version": "6.18.7", + "hash": "sha256:07lgbc0w7fd9akxmazhkpjgxhd3ffwh7in2nkchhdbprbk8s89mp", "lts": false } } From a5107b0c0f1c6aa9117ff6d209c3a6d834b73465 Mon Sep 17 00:00:00 2001 From: zowoq <59103226+zowoq@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:36:01 +0000 Subject: [PATCH 19/19] linux_6_12: 6.12.66 -> 6.12.67 --- pkgs/os-specific/linux/kernel/kernels-org.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index 1e462e03278c..596f230b0541 100644 --- a/pkgs/os-specific/linux/kernel/kernels-org.json +++ b/pkgs/os-specific/linux/kernel/kernels-org.json @@ -25,8 +25,8 @@ "lts": true }, "6.12": { - "version": "6.12.66", - "hash": "sha256:1bvfadb5149sh927f8cbr1rnypn7v0h6znjdrc1mmc30q2hrff5s", + "version": "6.12.67", + "hash": "sha256:0cn42b7pam5ssk740s3d42sp3cggl923yvs67y5fz98z5v35v00n", "lts": true }, "6.18": {