staging-nixos merge for 2026-01-23 (#483049)

This commit is contained in:
K900
2026-01-23 12:06:29 +00:00
committed by GitHub
28 changed files with 960 additions and 32 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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;

View File

@@ -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/<generated-driver-name>`.
'';
};
};
};
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" ];
};
};
}

View File

@@ -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
<https://github.com/boltgolt/howdy/blob/d3ab99382f88f043d15f15c1450ab69433892a1c/howdy/src/config.ini>
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;
}
];
}

View File

@@ -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
{

View File

@@ -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\""

View File

@@ -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;

View File

@@ -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"

View File

@@ -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;

View File

@@ -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 ];
};

View File

@@ -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
<nixpkgs/nixos/modules/testing/test-instrumentation.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 '<nixpkgs/nixos>' -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 '<nixpkgs/nixos>' -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}")
'';
}

View File

@@ -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")
'';
};
}

View File

@@ -79,6 +79,13 @@ stdenv.mkDerivation rec {
makeFlags = [ "udevruledir=$(out)/lib/udev/rules.d" ];
outputs = [
"out"
"dev"
"man"
"lib"
];
enableParallelBuilding = true;
doInstallCheck = true;

View File

@@ -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 ];
};
})

View File

@@ -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;
};
})

View File

@@ -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 = [

View File

@@ -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 ];

View File

@@ -43,7 +43,9 @@ stdenv.mkDerivation rec {
outputs = [
"out"
"doc"
"info"
"man"
];
configureFlags = [

View File

@@ -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

View File

@@ -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 { };

View File

@@ -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:

View File

@@ -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))

View File

@@ -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,

View File

@@ -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,
),
]
)

View File

@@ -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 ];

View File

@@ -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

View File

@@ -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
}
}