diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 6b7673a82df9..aa50affb51a2 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -38,6 +38,10 @@ - [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. + +- [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). @@ -52,6 +56,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/module-list.nix b/nixos/modules/module-list.nix index 69e3d66046d4..f2bb57ebcfeb 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 @@ -1489,6 +1490,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/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" ]; + }; + }; +} 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; + } + ]; +} 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 { diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix index cc7f1096e8b3..9bcb9bd0cb01 100644 --- a/nixos/modules/system/boot/resolved.nix +++ b/nixos/modules/system/boot/resolved.nix @@ -185,7 +185,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\"" 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" diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 8e087017d7e7..a5a0a2e93ee6 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -1770,13 +1770,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; 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/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") + ''; + }; + } 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; 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 ]; + }; +}) 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; + }; +}) 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 = [ 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 ]; 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 = [ 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/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)) 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, + ), + ] + ) 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 ]; diff --git a/pkgs/os-specific/linux/kernel/common-config.nix b/pkgs/os-specific/linux/kernel/common-config.nix index 5e90af00b2e9..e38ec3202941 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 diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index 5cb786e4c85b..596f230b0541 100644 --- a/pkgs/os-specific/linux/kernel/kernels-org.json +++ b/pkgs/os-specific/linux/kernel/kernels-org.json @@ -25,13 +25,13 @@ "lts": true }, "6.12": { - "version": "6.12.66", - "hash": "sha256:1bvfadb5149sh927f8cbr1rnypn7v0h6znjdrc1mmc30q2hrff5s", + "version": "6.12.67", + "hash": "sha256:0cn42b7pam5ssk740s3d42sp3cggl923yvs67y5fz98z5v35v00n", "lts": true }, "6.18": { - "version": "6.18.6", - "hash": "sha256:06x3z649mzwwkb1hvsy0yh7j5jk9qrnwqcmwy7dx8s1ggccrf927", + "version": "6.18.7", + "hash": "sha256:07lgbc0w7fd9akxmazhkpjgxhd3ffwh7in2nkchhdbprbk8s89mp", "lts": false } }