diff --git a/nixos/doc/manual/configuration/ad-hoc-network-config.section.md b/nixos/doc/manual/configuration/ad-hoc-network-config.section.md index 7ef24825526d..611f3e3956ee 100644 --- a/nixos/doc/manual/configuration/ad-hoc-network-config.section.md +++ b/nixos/doc/manual/configuration/ad-hoc-network-config.section.md @@ -1,14 +1,24 @@ # Ad-Hoc Configuration {#ad-hoc-network-config} -You can use [](#opt-networking.localCommands) to -specify shell commands to be run at the end of `network-setup.service`. This -is useful for doing network configuration not covered by the existing NixOS -modules. For instance, to statically configure an IPv6 address: +You can use [](#opt-networking.localCommands) to specify shell commands to be +run after the network interfaces have been created, but not necessarily fully +configured. +This is useful for doing network configuration not covered by the existing +NixOS modules. For example, you can create a network namespace and a pair +of virtual ethernet devices like this: ```nix { networking.localCommands = '' - ip -6 addr add 2001:610:685:1::1/64 dev eth0 + ip netns add mynet + ip link add name veth-in type veth peer name veth-out + ip link set dev veth-out netns mynet ''; } ``` + +::: {.note} +The commands should ideally be idempotent, so it's recommended to perform +cleanups of the state you create (e.g. virtual interfaces), or at least make +sure possible failures are handled. +::: diff --git a/nixos/doc/manual/configuration/ipv4-config.section.md b/nixos/doc/manual/configuration/ipv4-config.section.md index ed0db9da8c3e..ceb9a350b680 100644 --- a/nixos/doc/manual/configuration/ipv4-config.section.md +++ b/nixos/doc/manual/configuration/ipv4-config.section.md @@ -26,9 +26,16 @@ servers: ``` ::: {.note} -Statically configured interfaces are set up by the systemd service -`interface-name-cfg.service`. The default gateway and name server -configuration is performed by `network-setup.service`. +Addresses and routes for statically configured interfaces and the default +gateway are set up by systemd services named +`network-addresses-.service`. The name servers configuration, +instead, is performed by `network-local-commands.service` using resolvconf. +::: + +::: {.note} +If needed, for example if addresses/routes were added/removed, +you can reset the network configuration by running +`systemctl restart networking-scripted.target` ::: The host name is set using [](#opt-networking.hostName): diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 482d690ed62f..0b93852ee432 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -223,6 +223,13 @@ See . Note for NetworkManager users: before these changes NetworkManager used to spawn its own wpa_supplicant daemon, but now it relies on `networking.wireless`. So, if you had `networking.wireless.enable = false` in your configuration, you should remove that line. +- Some implementation details of the NixOS network-interfaces module have been changed: + + - In the "scripted" backend, `network-setup.service` has been removed and the network configuration services are now part of `network.target`, which is now directly pulled into `multi-user.target`. + - Interface addresses, routes and default gateways are now configured asynchronously as soon as the underlying network devices become available (fixes issue [#154737](https://github.com/NixOS/nixpkgs/issues/154737)). + - In both "networkd" and "scripted" backends, the configuration of name servers is now part of `network-local-commands.service` (fixes issue [#445496](https://github.com/NixOS/nixpkgs/issues/445496)). + - The issue that resulted in a completely unconfigured network if both `resolvconf` was disabled and no default gateway configured, has also been fixed. + - `kratos` has been updated from 1.3.1 to [25.4.0](https://github.com/ory/kratos/releases/tag/v25.4.0). Upstream switched to a new versioning scheme (year.major.minor). Notable breaking changes: - The `migrate sql` CLI command is now `migrate sql up` diff --git a/nixos/lib/test-driver/src/test_driver/driver.py b/nixos/lib/test-driver/src/test_driver/driver.py index 8cbcb052be49..9188e195cb35 100644 --- a/nixos/lib/test-driver/src/test_driver/driver.py +++ b/nixos/lib/test-driver/src/test_driver/driver.py @@ -228,6 +228,7 @@ class Driver: general_symbols = dict( start_all=self.start_all, test_script=self.test_script, + machines=self.machines, machines_qemu=self.machines_qemu, machines_nspawn=self.machines_nspawn, vlans=self.vlans, @@ -243,6 +244,8 @@ class Driver: serial_stdout_on=self.serial_stdout_on, polling_condition=self.polling_condition, BaseMachine=BaseMachine, # for typing + QemuMachine=QemuMachine, # for typing + NspawnMachine=NspawnMachine, # for typing t=AssertionTester(), debug=self.debug, ) @@ -369,7 +372,7 @@ class Driver: *, name: str | None = None, keep_machine_state: bool = False, - ) -> BaseMachine: + ) -> QemuMachine: """ Create a `QemuMachine`. This currently only supports qemu "nodes", not containers. """ diff --git a/nixos/lib/test-script-prepend.py b/nixos/lib/test-script-prepend.py index b9a12d5f2f54..e836e779300c 100644 --- a/nixos/lib/test-script-prepend.py +++ b/nixos/lib/test-script-prepend.py @@ -36,7 +36,7 @@ class CreateMachineProtocol(Protocol): name: Optional[str] = None, keep_machine_state: bool = False, **kwargs: Any, # to allow usage of deprecated keep_vm_state - ) -> BaseMachine: + ) -> QemuMachine: raise Exception("This is just type information for the Nix test driver") @@ -45,6 +45,8 @@ subtest: Callable[[str], ContextManager[None]] retry: RetryProtocol test_script: Callable[[], None] machines: List[BaseMachine] +machines_qemu: List[QemuMachine] +machines_nspawn: List[NspawnMachine] vlans: List[VLan] driver: Driver log: AbstractLogger diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index 9f5ad83e0746..d568b32a45a3 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -666,6 +666,137 @@ let }; }; + slurm = { + enable = lib.mkOption { + default = false; + type = lib.types.bool; + description = '' + If set, ONLY prevents users from logging into nodes if they have no + jobs in the node. This module is a legacy implementation with + functionality limited to login restrictions. + ''; + }; + + adopt = { + enable = lib.mkOption { + default = false; + type = lib.types.bool; + description = '' + If set, it prevents users from logging into nodes if they have no jobs + in the node. It also tracks any other spawned processes for accounting + and ensures complete job cleanup when a job is completed for any + successful connection. Spawned processes get "adopted" as external + steps into the current job. As such, those steps get integrated with + Slurm accounting and control group facilities. + ''; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = moduleSettingsType; + options = { + + action_no_jobs = lib.mkOption { + type = lib.types.enum [ + "ignore" + "deny" + ]; + default = "deny"; + description = '' + What to do if no jobs from the user are found, deny or ignore + (pass along to next PAM module). + ''; + }; + + action_unknown = lib.mkOption { + type = lib.types.enum [ + "newest" + "allow" + "deny" + ]; + default = "newest"; + description = '' + If the user has jobs, attach them to the newest job. Allow + the connection through without adoption. + ''; + }; + + action_adopt_failure = lib.mkOption { + type = lib.types.enum [ + "allow" + "deny" + ]; + default = "deny"; + description = '' + What to do if the process is unable to be adopted into a job. + `allow` matches the upstream default which is only really + suitable for testing; production systems will want `deny` + as a default. + ''; + }; + + action_generic_failure = lib.mkOption { + type = lib.types.enum [ + "ignore" + "allow" + "deny" + ]; + default = "ignore"; + description = '' + Catch all for failures related to kernel issues or slurmd + access. Ignore falls through to the next PAM module, allowing + the connection to go through without adoption. + ''; + }; + + disable_x11 = lib.mkOption { + type = lib.types.enum [ + "0" + "1" + ]; + default = "0"; + description = '' + Disable or enable x11 sessions. '0' means the adopted connection + has Slurm X11 forwarding with DISPLAY overwritten using X11 + tunnel endpoint details. + ''; + }; + + nodename = lib.mkOption { + type = with lib.types; nullOr nonEmptyStr; + default = null; + example = "compute-a-01"; + description = '' + Set this only when the Slurm `NodeName` for this machine + differs from `hostname -s`. If unset, `pam_slurm_adopt` + uses the host short name. + ''; + }; + + join_container = lib.mkOption { + type = lib.types.enum [ + "true" + "false" + ]; + default = "true"; + description = '' + Attach to a container created by job_container/tmpfs + ''; + }; + }; + }; + + default = { + service = name; + }; + description = '' + Slurm Adopt Settings. More information is available at: + - https://slurm.schedmd.com/pam_slurm_adopt.html + ''; + }; + }; + }; + zfs = lib.mkOption { default = config.security.pam.zfs.enable; defaultText = lib.literalExpression "config.security.pam.zfs.enable"; @@ -810,6 +941,12 @@ let control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; } + { + name = "slurm"; + enable = cfg.slurm.enable; + control = "required"; + modulePath = "${pkgs.slurm}/lib/security/pam_slurm.so"; + } # The required pam_unix.so module has to come after all the sufficient modules # because otherwise, the account lookup will fail if the user does not exist # locally, for example with MySQL- or LDAP-auth. @@ -818,6 +955,15 @@ let control = "required"; modulePath = "${package}/lib/security/pam_unix.so"; } + # pam_slurm_adopt must be the last module in the account stack. + { + name = "slurm_adopt"; + enable = cfg.slurm.adopt.enable; + control = "required"; + modulePath = "${pkgs.slurm}/lib/security/pam_slurm_adopt.so"; + settings = cfg.slurm.adopt.settings; + } + ]; auth = autoOrderRules ( diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl index f5ae844da21f..f61c91f02db8 100644 --- a/nixos/modules/system/boot/loader/grub/install-grub.pl +++ b/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -685,12 +685,14 @@ struct(GrubState => { efi => '$', devices => '$', efiMountPoint => '$', + grub => '$', + grubEfi => '$', extraGrubInstallArgs => '@', }); # If you add something to the state file, only add it to the end # because it is read line-by-line. sub readGrubState { - my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () ); + my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", grub => "", grubEfi => "", extraGrubInstallArgs => () ); open my $fh, "<", "$bootPath/grub/state" or return $defaultGrubState; local $/ = "\n"; my $name = <$fh>; @@ -721,8 +723,10 @@ sub readGrubState { } my %jsonState = %{decode_json($jsonStateLine)}; my @extraGrubInstallArgs = exists($jsonState{'extraGrubInstallArgs'}) ? @{$jsonState{'extraGrubInstallArgs'}} : (); + my $grubValue = exists($jsonState{'grub'}) ? $jsonState{'grub'} : ""; + my $grubEfiValue = exists($jsonState{'grubEfi'}) ? $jsonState{'grubEfi'} : ""; close $fh; - my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs ); + my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs, grub => $grubValue, grubEfi => $grubEfiValue ); return $grubState } @@ -734,15 +738,16 @@ my @prevExtraGrubInstallArgs = @{$prevGrubState->extraGrubInstallArgs}; my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference()); my $extraGrubInstallArgsDiffer = scalar (List::Compare->new( '-u', '-a', \@extraGrubInstallArgs, \@prevExtraGrubInstallArgs)->get_symmetric_difference()); -my $nameDiffer = get("fullName") ne $prevGrubState->name; -my $versionDiffer = get("fullVersion") ne $prevGrubState->version; my $efiDiffer = $efiTarget ne $prevGrubState->efi; my $efiMountPointDiffer = $efiSysMountPoint ne $prevGrubState->efiMountPoint; +# re-installing grub once the package store path changes is necessary, because +# introducing patches or adjusting builds does not always bump the version number +my $grubStorePathsDiffer = ($grub ne $prevGrubState->grub) || ($grubEfi ne $prevGrubState->grubEfi); if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") { warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER"; $ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1"; } -my $requireNewInstall = $devicesDiffer || $extraGrubInstallArgsDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1"); +my $requireNewInstall = $devicesDiffer || $extraGrubInstallArgsDiffer || $efiDiffer || $efiMountPointDiffer || $grubStorePathsDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1"); # install a symlink so that grub can detect the boot drive my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space: $!"; @@ -795,7 +800,9 @@ if ($requireNewInstall != 0) { print $fh join( ",", @deviceTargets ), "\n" or die; print $fh $efiSysMountPoint, "\n" or die; my %jsonState = ( - extraGrubInstallArgs => \@extraGrubInstallArgs + extraGrubInstallArgs => \@extraGrubInstallArgs, + grub => $grub, + grubEfi => $grubEfi ); my $jsonStateLine = encode_json(\%jsonState); print $fh $jsonStateLine, "\n" or die; diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix index 4b457e078e77..4bf274d4157f 100644 --- a/nixos/modules/tasks/network-interfaces-scripted.nix +++ b/nixos/modules/tasks/network-interfaces-scripted.nix @@ -51,6 +51,71 @@ let (lib.concatStringsSep " ") ]; + # Converts an IPv4 address literal to a list of bits + parseAddr.ipv4 = + addr: + let + pad = b: lib.replicate (8 - builtins.length b) 0 ++ b; + toBin = n: pad (lib.toBaseDigits 2 (lib.toInt n)); + in + lib.concatMap toBin (builtins.splitVersion addr); + + # Converts an IPv6 address literal to a list of bits + parseAddr.ipv6 = + addr: + let + pad = b: lib.replicate (16 - builtins.length b) 0 ++ b; + fromHex = n: (builtins.fromTOML "n = 0x${n}").n; + toBin = n: pad (lib.toBaseDigits 2 (fromHex n)); + normal = (lib.network.ipv6.fromString addr).address; + in + lib.concatMap toBin (lib.splitString ":" normal); + + # Checks if `addr` is part of the `net` subnet + inSubnet = + v: net: addr: + let + prefix = lib.take net.prefixLength (parseAddr.${v} net.address); + match = lib.zipListsWith (a: b: a == b) prefix (parseAddr.${v} addr); + in + lib.all lib.id match; + + # Checks if the netmask of all addresses on interface `iface` includes + # the IP address of `gateway` + # + # Note: this is used to check whether networking.defaultGateway relies on + # the given interface, either explicitly, via the `interface` (optional), + # or explicitly, by using an address in a subnet of this interface. + # + # Configuration of the default gateway is then performed as part of that + # interface setup in `configureAddrs`, below. + isGateway = + v: gateway: iface: + lib.any lib.id ( + [ (iface.name == gateway.interface) ] + ++ map (net: inSubnet v net gateway.address) iface.${v}.addresses + ); + + # Checks if `gateway` uses an address from `iface` as default source + # + # Note: this is needed to delay the configuration of the gateway and default + # source until the right interfaces and address have been set up, otherwise + # the commands will fail. + hasSource = + v: gateway: iface: + builtins.elem gateway.source (map (i: i.address) iface.${v}.addresses); + + # Interfaces corresponding to the default gateways + gateway4Iface = builtins.filter (isGateway "ipv4" cfg.defaultGateway) interfaces; + gateway6Iface = builtins.filter (isGateway "ipv6" cfg.defaultGateway6) interfaces; + + # Interfaces corresponding to the default source addresses + # + # Note: the use of `head` here is safe because these expressions + # are evaluated only when `needsSourceIface`, see `configureAddrs` below. + source4Iface = builtins.head (builtins.filter (hasSource "ipv4" cfg.defaultGateway) interfaces); + source6Iface = builtins.head (builtins.filter (hasSource "ipv6" cfg.defaultGateway6) interfaces); + # warn that these attributes are deprecated (2017-2-2) # Should be removed in the release after next bondDeprecation = rec { @@ -118,121 +183,71 @@ let else optional (!config.boot.isContainer) (subsystemDevice dev); - hasDefaultGatewaySet = - (cfg.defaultGateway != null && cfg.defaultGateway.address != "") - || (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != ""); - - needNetworkSetup = - cfg.resolvconf.enable || cfg.defaultGateway != null || cfg.defaultGateway6 != null; - - networkLocalCommands = lib.mkIf needNetworkSetup { - after = [ "network-setup.service" ]; - bindsTo = [ "network-setup.service" ]; - }; - - networkSetup = lib.mkIf needNetworkSetup { - description = "Networking Setup"; - - after = [ "network-pre.target" ]; - before = [ - "network.target" - "shutdown.target" - ]; - wants = [ "network.target" ]; - # exclude bridges from the partOf relationship to fix container networking bug #47210 - partOf = map (i: "network-addresses-${i.name}.service") ( - filter (i: !(hasAttr i.name cfg.bridges)) interfaces - ); - conflicts = [ "shutdown.target" ]; - wantedBy = [ "multi-user.target" ] ++ optional hasDefaultGatewaySet "network-online.target"; - - unitConfig.ConditionCapability = "CAP_NET_ADMIN"; - - path = [ pkgs.iproute2 ]; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - - unitConfig.DefaultDependencies = false; - - script = '' - ${optionalString config.networking.resolvconf.enable '' - # Set the static DNS configuration, if given. - ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <, create a job ‘network-addresses-.service" - # that performs static address configuration. It has a "wants" - # dependency on ‘.service’, which is supposed to create - # the interface and need not exist (i.e. for hardware - # interfaces). It has a binds-to dependency on the actual - # network device, so it only gets started after the interface - # has appeared, and it's stopped when the interface - # disappears. + # For each interface , creates a network-addresses-.service + # job that performs static address configuration. + # + # It has a Wants dependency on -netdev.service, which creates + # create the interface, or on a device unit (for hardware interfaces). + # It also has a BindsTo dependency on the device unit: so, it only gets + # started after the interface has appeared and it's stopped when the + # interface disappears. + # + # Unless in a container, the job is not made part of network.target, so + # if an interface is not found (e.g. a USB interface not plugged in) it + # will not hang the boot sequence. + # + # If the interface is the default gateway, the job will also set the + # default gateway and delay network-online.target. configureAddrs = i: let ips = interfaceIps i; + isDefaultGateway4 = cfg.defaultGateway != null && builtins.elem i gateway4Iface; + isDefaultGateway6 = cfg.defaultGateway6 != null && builtins.elem i gateway6Iface; + needsSourceIface4 = + isDefaultGateway4 && cfg.defaultGateway.source != null && i.name != source4Iface.name; + needsSourceIface6 = + isDefaultGateway6 && cfg.defaultGateway6.source != null && i.name != source6Iface.name; + + configureGateway = + version: gateway: + optionalString (gateway.address != "") '' + echo -n "setting ${i.name} as default IPv${version} gateway... " + ${optionalString (gateway.interface != null) '' + ip -${version} route replace ${gateway.address} proto static ${ + formatIpArgs { + metric = gateway.metric; + dev = gateway.interface; + } + } + ''} + ip -${version} route replace default proto static ${ + formatIpArgs { + metric = gateway.metric; + via = gateway.address; + window = cfg.defaultGatewayWindowSize; + dev = gateway.interface; + src = gateway.source; + } + } + echo "done" + ''; in nameValuePair "network-addresses-${i.name}" { description = "Address configuration of ${i.name}"; - wantedBy = [ - "network-setup.service" - "network.target" - ]; - # order before network-setup because the routes that are configured - # there may need ip addresses configured - before = [ "network-setup.service" ]; + + wantedBy = + deviceDependency i.name + ++ optional config.boot.isContainer "network.target" + ++ optional (isDefaultGateway4 || isDefaultGateway6) "network-online.target"; bindsTo = deviceDependency i.name; - after = [ "network-pre.target" ] ++ (deviceDependency i.name); + partOf = [ "networking-scripted.target" ]; + after = [ + "network-pre.target" + ] + ++ optional needsSourceIface4 "network-addresses-${source4Iface.name}.service" + ++ optional needsSourceIface6 "network-addresses-${source6Iface.name}.service" + ++ deviceDependency i.name; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; # Restart rather than stop+start this unit to prevent the @@ -284,6 +299,10 @@ let fi '' )} + + # Set the default gateway + ${optionalString isDefaultGateway4 (configureGateway "4" cfg.defaultGateway)} + ${optionalString isDefaultGateway6 (configureGateway "6" cfg.defaultGateway6)} ''; preStop = '' state="/run/nixos/network/routes/${i.name}" @@ -311,13 +330,13 @@ let nameValuePair "${i.name}-netdev" { description = "Virtual Network Interface ${i.name}"; bindsTo = optional (!config.boot.isContainer) "dev-net-tun.device"; + partOf = [ "networking-scripted.target" ]; after = optional (!config.boot.isContainer) "dev-net-tun.device" ++ [ "network-pre.target" ]; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice i.name) ]; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; path = [ pkgs.iproute2 ]; serviceConfig = { Type = "oneshot"; @@ -343,18 +362,21 @@ let description = "Bridge Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps ++ optional v.rstp "mstpd.service"; - partOf = [ "network-setup.service" ] ++ optional v.rstp "mstpd.service"; + partOf = [ + "network.target" + "networking-scripted.target" + ] + ++ optional v.rstp "mstpd.service"; after = [ "network-pre.target" ] ++ deps ++ optional v.rstp "mstpd.service" ++ map (i: "network-addresses-${i}.service") v.interfaces; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -448,15 +470,14 @@ let description = "Open vSwitch Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ] ++ internalConfigs; - # before = [ "network-setup.service" ]; - # should work without internalConfigs dependencies because address/link configuration depends - # on the device, which is created by ovs-vswitchd with type=internal, but it does not... - before = [ "network-setup.service" ] ++ internalConfigs; - partOf = [ "network-setup.service" ]; # shutdown the bridge when network is shutdown + before = [ "network.target" ] ++ internalConfigs; + partOf = [ + "network.target" + "networking-scripted.target" + ]; # shutdown the bridge when network is shutdown bindsTo = [ "ovs-vswitchd.service" ]; # requires ovs-vswitchd to be alive at all times after = [ "network-pre.target" @@ -521,12 +542,12 @@ let description = "Bond Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps ++ map (i: "network-addresses-${i}.service") v.interfaces; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ @@ -570,12 +591,12 @@ let description = "MACVLAN Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -602,12 +623,12 @@ let description = "IPVLAN Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -647,12 +668,12 @@ let description = "FOU endpoint ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -677,12 +698,11 @@ let description = "IPv6 in IPv4 Tunnel Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -720,12 +740,12 @@ let description = "IP in IP Tunnel Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -768,12 +788,12 @@ let description = "GRE Tunnel Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; + partOf = [ "networking-scripted.target" ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -803,13 +823,15 @@ let description = "VLAN Interface ${n}"; wantedBy = [ "network.target" - "network-setup.service" (subsystemDevice n) ]; bindsTo = deps; - partOf = [ "network-setup.service" ]; + partOf = [ + "network.target" + "networking-scripted.target" + ]; after = [ "network-pre.target" ] ++ deps; - before = [ "network-setup.service" ]; + before = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; path = [ pkgs.iproute2 ]; @@ -845,10 +867,27 @@ let // mapAttrs' createGreDevice cfg.greTunnels // mapAttrs' createVlanDevice cfg.vlans // { - network-setup = networkSetup; - network-local-commands = networkLocalCommands; + network-local-commands = { + after = [ "network-pre.target" ]; + wantedBy = [ "network.target" ]; + }; }; + # Note: the scripted networking backend consistent of many + # independent services that are linked to the network.target. + # Since there is no daemon (e.g systemd-networkd) that is + # started as part of the system and pulls in network.target. + # Thus, to start these services we link network.target directly + # to multi-user.target, this has the same result. + systemd.targets.network.wantedBy = [ "multi-user.target" ]; + + # This target serves no purpose during the boot, but can be + # used to quickly reset the network configuration by running + # systemctl restart networking-scripted.target + systemd.targets.networking-scripted = { + description = "NixOS scripted networking setup"; + }; + services.udev.extraRules = '' KERNEL=="tun", TAG+="systemd" ''; diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 83d60ba80e40..d0fe0ed8443f 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -747,10 +747,9 @@ in default = ""; example = "text=anything; echo You can put $text here."; description = '' - Shell commands to be executed at the end of the - `network-setup` systemd service. Note that if - you are using DHCP to obtain the network configuration, - interfaces may not be fully configured yet. + Shell commands to be executed after all the network + interfaces have been created, but not necessarily + fully configured. ''; }; @@ -1851,6 +1850,20 @@ in ''; }; }; + + networking.localCommands = lib.mkIf config.networking.resolvconf.enable '' + # Set the static DNS configuration, if given. + ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static < GarageNode: + def get_node_fqn(machine: BaseMachine) -> GarageNode: node_id, host = machine.succeed("garage node id").split('@') return GarageNode(node_id=node_id, host=host) - def get_node_id(machine: Machine) -> str: + def get_node_id(machine: BaseMachine) -> str: return get_node_fqn(machine).node_id - def get_layout_version(machine: Machine) -> int: + def get_layout_version(machine: BaseMachine) -> int: version_data = machine.succeed("garage layout show") m = cur_version_regex.search(version_data) if m and m.group('ver') is not None: @@ -36,17 +36,17 @@ else: raise ValueError('Cannot find current layout version') - def apply_garage_layout(machine: Machine, layouts: List[str]): + def apply_garage_layout(machine: BaseMachine, layouts: List[str]): for layout in layouts: machine.succeed(f"garage layout assign {layout}") version = get_layout_version(machine) machine.succeed(f"garage layout apply --version {version}") - def create_api_key(machine: Machine, key_name: str) -> S3Key: + def create_api_key(machine: BaseMachine, key_name: str) -> S3Key: output = machine.succeed(f"garage key create {key_name}") return parse_api_key_data(output) - def get_api_key(machine: Machine, key_pattern: str) -> S3Key: + def get_api_key(machine: BaseMachine, key_pattern: str) -> S3Key: output = machine.succeed(f"garage key info {key_pattern}") return parse_api_key_data(output) diff --git a/nixos/tests/incus/incus_machine.py b/nixos/tests/incus/incus_machine.py index c25a7804b871..eeeb0bdecf4c 100644 --- a/nixos/tests/incus/incus_machine.py +++ b/nixos/tests/incus/incus_machine.py @@ -1,7 +1,7 @@ import json -class IncusHost(Machine): +class IncusHost(QemuMachine): def __init__(self, base): with subtest("Wait for startup"): base.wait_for_unit("incus.service") diff --git a/nixos/tests/networking-proxy.nix b/nixos/tests/networking-proxy.nix index 2f8a64b5affa..ec784f2bd657 100644 --- a/nixos/tests/networking-proxy.nix +++ b/nixos/tests/networking-proxy.nix @@ -72,7 +72,7 @@ in from typing import Dict, Optional - def get_machine_env(machine: Machine, user: Optional[str] = None) -> Dict[str, str]: + def get_machine_env(machine: BaseMachine, user: Optional[str] = None) -> Dict[str, str]: """ Gets the environment from a given machine, and returns it as a dictionary in the form: diff --git a/nixos/tests/networking/networkd-and-scripted.nix b/nixos/tests/networking/networkd-and-scripted.nix index 99e67649ceb3..d620e9b230fb 100644 --- a/nixos/tests/networking/networkd-and-scripted.nix +++ b/nixos/tests/networking/networkd-and-scripted.nix @@ -44,17 +44,13 @@ let defaultGateway6 = { address = "fd00:1234:5678:1::1"; interface = "enp1s0"; - source = "fd00:1234:5678:1::3"; + source = "fd00:1234:5678:1::3"; # implicit dependency on enp2s0 }; interfaces.enp1s0.ipv6.addresses = [ { address = "fd00:1234:5678:1::2"; prefixLength = 64; } - { - address = "fd00:1234:5678:1::3"; - prefixLength = 128; - } ]; interfaces.enp1s0.ipv4.addresses = [ { @@ -76,6 +72,12 @@ let prefixLength = 24; } ]; + interfaces.enp2s0.ipv6.addresses = [ + { + address = "fd00:1234:5678:1::3"; + prefixLength = 128; + } + ]; }; }; testScript = '' @@ -108,6 +110,41 @@ let client.succeed("ip -6 route show default | grep -q 'src fd00:1234:5678:1::3'") ''; }; + dynamicInterface = { + name = "dynamicInterface"; + nodes.machine = clientConfig { + networking.interfaces.usb0 = { + ipv6.addresses = lib.singleton { + address = "fd::1"; + prefixLength = 127; + }; + }; + networking.defaultGateway6 = { + address = "fd::"; + interface = "usb0"; + source = "fd::1"; + }; + }; + testScript = '' + with subtest("Network comes up without usb0"): + machine.wait_for_unit("network.target") + + with subtest("multi-user.target does not hang"): + machine.require_unit_state("multi-user.target", "active") + + with subtest("usb0 is configured when plugged in"): + machine.succeed("ip link add usb0 type sit local 1.2.3.4") + machine.wait_until_succeeds("ip addr show dev usb0 | grep -q fd::1") + + with subtest("Network is now online"): + machine.systemctl("start network-online.target") + machine.require_unit_state("network-online.target", "active") + + with subtest("Default gateway is now set"): + machine.succeed("ip -6 route show default | grep -q 'via fd::'") + machine.succeed("ip -6 route show default | grep -q 'src fd::1'") + ''; + }; routeType = { name = "RouteType"; nodes.client = clientConfig { diff --git a/nixos/tests/pacemaker.nix b/nixos/tests/pacemaker.nix index 826c4be7df14..8023d6f14bc8 100644 --- a/nixos/tests/pacemaker.nix +++ b/nixos/tests/pacemaker.nix @@ -86,7 +86,7 @@ rec { output = node1.succeed("crm_resource -r cat --locate") match = re.search("is running on: (.+)", output) if match: - for machine in machines: + for machine in machines_qemu: if machine.name == match.group(1): current_node = machine break @@ -96,7 +96,7 @@ rec { current_node.crash() # pick another node that's still up - for machine in machines: + for machine in machines_qemu: if machine.booted: check_node = machine # find where the service has been started next @@ -105,7 +105,7 @@ rec { match = re.search("is running on: (.+)", output) # output will remain the old current_node until the crash is detected by pacemaker if match and match.group(1) != current_node.name: - for machine in machines: + for machine in machines_qemu: if machine.name == match.group(1): next_node = machine break diff --git a/nixos/tests/password-option-override-ordering.nix b/nixos/tests/password-option-override-ordering.nix index caa0246735ca..71c449064a0b 100644 --- a/nixos/tests/password-option-override-ordering.nix +++ b/nixos/tests/password-option-override-ordering.nix @@ -112,22 +112,22 @@ in assert stored_hash == pass_hash, f"{username} user password does not match" with subtest("alice user has correct password"): - for machine in machines: + for machine in machines_qemu: assert_password_sha512crypt_match(machine, "alice", "${password1}") assert "${hashed_sha512crypt}" not in machine.succeed("getent shadow alice"), f"{machine}: alice user password is not correct" with subtest("bob user has correct password"): - for machine in machines: + for machine in machines_qemu: print(machine.succeed("getent shadow bob")) assert "${hashed_bcrypt}" in machine.succeed("getent shadow bob"), f"{machine}: bob user password is not correct" with subtest("cat user has correct password"): - for machine in machines: + for machine in machines_qemu: print(machine.succeed("getent shadow cat")) assert "${hashed_bcrypt}" in machine.succeed("getent shadow cat"), f"{machine}: cat user password is not correct" with subtest("dan user has correct password"): - for machine in machines: + for machine in machines_qemu: print(machine.succeed("getent shadow dan")) assert "${hashed_bcrypt}" in machine.succeed("getent shadow dan"), f"{machine}: dan user password is not correct" @@ -138,11 +138,11 @@ in assert_password_sha512crypt_match(immutable, "greg", "${password1}") assert "${hashed_sha512crypt}" not in immutable.succeed("getent shadow greg"), "greg user password is not correct" - for machine in machines: + for machine in machines_qemu: machine.wait_for_unit("multi-user.target") machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'") - def check_login(machine: Machine, tty_number: str, username: str, password: str): + def check_login(machine: QemuMachine, tty_number: str, username: str, password: str): machine.send_key(f"alt-f{tty_number}") machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]") machine.wait_for_unit(f"getty@tty{tty_number}.service") @@ -158,11 +158,11 @@ in assert username in machine.succeed(f"cat /tmp/{tty_number}"), f"{machine}: {username} password is not correct" with subtest("Test initialPassword override"): - for machine in machines: + for machine in machines_qemu: check_login(machine, "2", "egon", "${password1}") with subtest("Test initialHashedPassword override"): - for machine in machines: + for machine in machines_qemu: check_login(machine, "3", "fran", "meow") ''; } diff --git a/nixos/tests/rtkit.nix b/nixos/tests/rtkit.nix index d884a588a16e..74bf69e02a83 100644 --- a/nixos/tests/rtkit.nix +++ b/nixos/tests/rtkit.nix @@ -103,7 +103,7 @@ Result = namedtuple("Result", ["command", "machine", "status", "out", "value"]) Value = namedtuple("Value", ["type", "data"]) - def busctl(node: Machine, *args: Any, user: Optional[str] = None) -> Result: + def busctl(node: BaseMachine, *args: Any, user: Optional[str] = None) -> Result: command = f"busctl --json=short {shlex.join(map(str, args))}" if user is not None: command = f"su - {user} -c {shlex.quote(command)}" @@ -121,7 +121,7 @@ if result.status == 0: raise Exception(f"command `{result.command}` unexpectedly succeeded") - def rtkit_make_process_realtime(node: Machine, pid: int, priority: int, user: Optional[str] = None) -> Result: + def rtkit_make_process_realtime(node: BaseMachine, pid: int, priority: int, user: Optional[str] = None) -> Result: return busctl(node, "call", "org.freedesktop.RealtimeKit1", "/org/freedesktop/RealtimeKit1", "org.freedesktop.RealtimeKit1", "MakeThreadRealtimeWithPID", "ttu", pid, 0, priority, user=user) def get_max_realtime_priority() -> int: @@ -133,7 +133,7 @@ def parse_chrt(out: str, field: str) -> str: return next(map(lambda l: l.split(": ")[1], filter(lambda l: field in l, out.splitlines()))) - def get_pid(node: Machine, unit: str, user: Optional[str] = None) -> int: + def get_pid(node: BaseMachine, unit: str, user: Optional[str] = None) -> int: node.wait_for_unit(unit, user=user) (status, out) = node.systemctl(f"show -P MainPID {unit}", user=user) if status == 0: @@ -142,7 +142,7 @@ node.log(out) raise Exception(f"unable to determine MainPID of {unit} (systemctl exit code {status})") - def assert_sched(node: Machine, pid: int, policy: Optional[str] = None, priority: Optional[int] = None): + def assert_sched(node: BaseMachine, pid: int, policy: Optional[str] = None, priority: Optional[int] = None): out = node.succeed(f"chrt -p {pid}") node.log(out) if policy is not None: diff --git a/nixos/tests/slurm-pam.nix b/nixos/tests/slurm-pam.nix new file mode 100644 index 000000000000..1bb613af38a0 --- /dev/null +++ b/nixos/tests/slurm-pam.nix @@ -0,0 +1,411 @@ +{ lib, pkgs, ... }: + +let + + slurmConf = "/etc/slurm.conf"; + inherit (import ./ssh-keys.nix pkgs) + snakeOilPrivateKey + snakeOilPublicKey + ; + sshConf = '' + UserKnownHostsFile /dev/null + StrictHostKeyChecking no + ''; + sshConfigPubkey = pkgs.writeText "ssh_config_pubkey" ( + sshConf + + '' + + BatchMode yes + PreferredAuthentications publickey + KbdInteractiveAuthentication no + PasswordAuthentication no + IdentityFile /root/privkey.snakeoil + '' + ); + sshConfigPassword = pkgs.writeText "ssh_config_password" ( + sshConf + + '' + BatchMode no + PreferredAuthentications password + PubkeyAuthentication no + KbdInteractiveAuthentication no + PasswordAuthentication yes + '' + ); + sshOpts = "-F " + sshConfigPubkey; + sshPassOpts = "-F " + sshConfigPassword; + adoptRemoteScript = pkgs.writeShellScript "slurm-pam-adopt-remote" '' + echo $$ > /home/submitter/ssh.pid + trap : TERM INT + while true; do + sleep 1 + done + ''; + mkWaitJob = + node: name: + pkgs.writeText "${name}.sbatch" '' + #!${pkgs.runtimeShell} + #SBATCH --job-name=${name} + #SBATCH --nodes=1 + #SBATCH --nodelist=${node} + while true; do sleep 60; done + ''; + + slurmconfig = + { config, ... }: + { + services.slurm = { + controlMachine = "control"; + nodeName = map (n: n + " CPUs=1 State=UNKNOWN") [ + "regular" + "pamslurm" + "pamslurmadopt" + ]; + partitionName = [ "debug Nodes=ALL Default=YES MaxTime=INFINITE State=UP" ]; + extraConfig = '' + PrologFlags=contain + ProctrackType=proctrack/cgroup + TaskPlugin=task/cgroup,task/affinity + SlurmdDebug=debug + ''; + }; + + services.openssh = { + enable = true; + settings = { + AllowUsers = [ "submitter" ]; + PubkeyAuthentication = true; + # leave password auth available on the regular node for + # regression testing plain pam_unix behavior + KbdInteractiveAuthentication = false; + PasswordAuthentication = true; + }; + }; + + environment.systemPackages = [ + pkgs.pamtester + pkgs.sshpass + ]; + + networking.firewall.enable = false; + systemd.tmpfiles.rules = [ + "f /etc/munge/munge.key 0400 munge munge - mungeverryweakkeybuteasytointegratoinatest" + ]; + environment.etc."slurm.conf".source = "${config.services.slurm.etcSlurm}/slurm.conf"; + systemd.services.sshd.environment.SLURM_CONF = slurmConf; + users.groups.submitter = { }; + users.users.submitter = { + isNormalUser = true; + createHome = true; + initialPassword = "submitter"; + group = "submitter"; + openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + }; + }; + +in +{ + name = "slurm-pam"; + + meta.maintainers = [ lib.maintainers.edwtjo ]; + + nodes = + let + computeNode = + { ... }: + { + imports = [ slurmconfig ]; + services.slurm = { + client.enable = true; + }; + }; + computePAMNode = + { ... }: + { + imports = [ computeNode ]; + security.pam.services.sshd.slurm.enable = true; + services.openssh.settings = { + KbdInteractiveAuthentication = false; + PasswordAuthentication = lib.mkForce false; + PubkeyAuthentication = true; + }; + }; + computePAMAdoptNode = + { ... }: + { + imports = [ computeNode ]; + # NOTE: Prolog, Epilog needed for more advanced tests. + # This is an upstream recommended workaround for removing pam_systemd + services.slurm.extraConfig = '' + LaunchParameters=ulimit_pam_adopt + SrunProlog=${pkgs.writers.writeBash "slurm-prolog" '' + loginctl enable-linger $SLURM_JOB_USER + exit 0 + ''} + TaskProlog=${pkgs.writers.writeBash "slurm-taskprolog" '' + echo "export XDG_RUNTIME_DIR=/run/user/$SLURM_JOB_UID" + echo "export XDG_SESSION_ID=$(/tmp/wait-pamslurm.out 2>/tmp/wait-pamslurm.err &\"" + ) + submit.wait_until_succeeds( + "squeue -h -u submitter -o '%N %u %T %j' | " + "grep -Fx 'pamslurm submitter RUNNING wait-pamslurm'" + ) + submit.fail( + "ssh ${sshOpts} submitter@pamslurmadopt true", + timeout=30 + ) + submit.succeed("runuser -u submitter -- scancel -n wait-pamslurm") + submit.wait_until_succeeds( + "! squeue -h -u submitter -o '%j' | grep -Fx wait-pamslurm" + ) + + # Positive integration tests for `pam_slurm_adopt`. + # + # This subtest checks the following: + # + # 1. a job can be created specifically on `pamslurmadopt`; + # 2. Slurm reports that the job is running on that node; + # 3. the node has local Slurm state for that job (`scontrol listpids`); + # 4. SSH login is allowed while the job is active; + # 5. a long-lived SSH session is adopted into Slurm's job tracking; + # 6. cancelling the job tears down the adopted process; + # 7. SSH access is denied again once the job is gone. + # + # Validates both policy enforcement and process adoption/cleanup. + with subtest("pam_slurm_adopt_adopts_connection"): + pamslurmadopt_job = submit.succeed( + "runuser -u submitter -- sbatch --parsable ${mkWaitJob "pamslurmadopt" "wait-pamslurmadopt"}" + ).strip() + + submit.wait_until_succeeds( + f"test \"$(squeue -h -j {pamslurmadopt_job} -o %T)\" = RUNNING" + ) + submit.wait_until_succeeds( + f"squeue -h -j {pamslurmadopt_job} -o %N | grep -Fx pamslurmadopt" + ) + pamslurmadopt.wait_until_succeeds( + f"scontrol listpids | awk -v jid='{pamslurmadopt_job}' 'NR > 1 && $2 == jid {{ found = 1 }} END {{ exit !found }}'" + ) + + # Short SSH probe: verifies that login is allowed at all while the job + # is active, before we move on to the stronger adoption assertions. + submit.succeed( + "ssh ${sshOpts} submitter@pamslurmadopt true", + timeout=30 + ) + + # Start a persistent SSH session whose remote process records its PID. + # We later use that PID to prove the session was adopted into Slurm's + # accounting/control path and then cleaned up when the job is cancelled. + submit.succeed( + "sh -lc '" + "ssh ${sshOpts} submitter@pamslurmadopt ${adoptRemoteScript}" + ">/tmp/adopt-ssh.log 2>&1 & " + "echo -n \\$! > /tmp/adopt-ssh.clientpid'" + ) + + # Wait until the remote helper has started and published its PID. + pamslurmadopt.wait_until_succeeds("test -s /home/submitter/ssh.pid") + + # Prove that the SSH-spawned remote process is visible through Slurm's + # local process listing, i.e. that the session was adopted into the job. + pamslurmadopt.wait_until_succeeds( + "pid=$(cat /home/submitter/ssh.pid); " + "scontrol listpids | awk 'NR > 1 { print $1 }' | grep -Fx \"$pid\"" + ) + remote_pid = pamslurmadopt.succeed("cat /home/submitter/ssh.pid").strip() + + # Cancel the allocation and verify the entire chain is torn down: + # the Slurm job disappears, the adopted remote process exits, and the + # local SSH client exits as well. + submit.succeed(f"runuser -u submitter -- scancel {pamslurmadopt_job}") + submit.wait_until_succeeds( + f"test -z \"$(squeue -h -j {pamslurmadopt_job})\"", + timeout=60, + ) + pamslurmadopt.wait_until_succeeds( + f"! test -e /proc/{remote_pid}", + timeout=60, + ) + submit.wait_until_succeeds( + "! kill -0 $(cat /tmp/adopt-ssh.clientpid)", + timeout=60, + ) + + # Once the job is gone, SSH must be denied + # again on the adopt-protected node. + submit.fail( + "ssh ${sshOpts} submitter@pamslurmadopt true", + timeout=30 + ) + ''; +} diff --git a/nixos/tests/slurm.nix b/nixos/tests/slurm.nix index b468e32e45e0..06b622739852 100644 --- a/nixos/tests/slurm.nix +++ b/nixos/tests/slurm.nix @@ -148,6 +148,8 @@ in }; testScript = '' + start_all() + with subtest("can_start_slurmdbd"): dbd.wait_for_unit("slurmdbd.service") dbd.wait_for_open_port(6819) @@ -155,36 +157,82 @@ in with subtest("cluster_is_initialized"): control.wait_for_unit("multi-user.target") control.wait_for_unit("slurmctld.service") - control.wait_until_succeeds("sacctmgr list cluster | awk '{ print $1 }' | grep default") + control.wait_for_open_port(6817) - start_all() - - with subtest("can_start_slurmd"): for node in [node1, node2, node3]: - node.wait_for_unit("slurmd") + node.wait_for_unit("slurmd.service") + node.wait_for_open_port(6818) - # Test that the cluster works and can distribute jobs; - submit.wait_for_unit("multi-user.target") + submit.wait_for_unit("multi-user.target") + + control.wait_until_succeeds( + "sacctmgr -nP list cluster format=cluster | grep -qx default" + ) + + # Test that the cluster works and can distribute jobs; + control.wait_until_succeeds( + "sinfo -Nh -o '%N %T' | grep -Fx 'node1 idle' && " + "sinfo -Nh -o '%N %T' | grep -Fx 'node2 idle' && " + "sinfo -Nh -o '%N %T' | grep -Fx 'node3 idle'" + ) with subtest("run_distributed_command"): # Run `hostname` on 3 nodes of the partition (so on all the 3 nodes). # The output must contain the 3 different names - submit.succeed("srun -N 3 hostname | sort | uniq | wc -l | xargs test 3 -eq") + submit.succeed( + "test \"$(srun -J distributed-hostname-check -N 3 hostname | sort -u | tr '\n' ' ')\" = 'node1 node2 node3 '" + ) - with subtest("check_slurm_dbd_job"): - # find the srun job from above in the database - control.wait_until_succeeds("sacct | grep hostname") + with subtest("check_slurm_dbd_job_for_srun"): + # find the srun job from above in the database + submit.wait_until_succeeds( + "sacct -X -P -n --name=distributed-hostname-check -o JobName,State | " + "grep -Eq '^distributed-hostname-check\\|COMPLETED(\\+.*)?$'" + ) with subtest("run_PMIx_mpitest"): - submit.succeed("srun -N 3 --mpi=pmix mpitest | grep size=3") + submit.succeed( + "out=$(srun -N 3 --mpi=pmix mpitest); " + "echo \"$out\"; " + "echo \"$out\" | grep -Fx 'size=3'; " + "test \"$(echo \"$out\" | grep -c 'hello world from process')\" -eq 3" + ) with subtest("run_sbatch"): - submit.succeed("sbatch --wait ${sbatchScript}") - submit.succeed("grep 'sbatch success' ${sbatchOutput}") + submit.succeed( + "jobid=$(sbatch --parsable --wait ${sbatchScript}); " + "echo \"$jobid\" > /tmp/sbatch.jobid" + ) + submit.succeed("grep -Fx 'sbatch success' ${sbatchOutput}") + submit.wait_until_succeeds( + "sacct -X -j $(cat /tmp/sbatch.jobid) -n -o State | grep -Eq 'COMPLETED|COMPLETED\\+'" + ) + submit.succeed("test -z \"$(squeue -h)\"") + + with subtest("cluster_returns_to_idle"): + control.wait_until_succeeds( + "sinfo -Nh -o '%N %T' | grep -Fx 'node1 idle' && " + "sinfo -Nh -o '%N %T' | grep -Fx 'node2 idle' && " + "sinfo -Nh -o '%N %T' | grep -Fx 'node3 idle'" + ) with subtest("rest"): rest.wait_for_unit("slurmrestd.service") - token = control.succeed("scontrol token").split('=')[1].rstrip() - rest.succeed("${pkgs.curl}/bin/curl -sk -H X-SLURM-USER-TOKEN:%s -X GET 'http://localhost:6820/slurm/v0.0.43/diag'" % token) + rest.wait_for_open_port(6820) + + token = control.succeed("scontrol token").split('=', 1)[1].strip() + + rest.succeed( + "${pkgs.curl}/bin/curl -fsS " + "-H X-SLURM-USER-TOKEN:%s " + "http://localhost:6820/slurm/v0.0.43/diag | grep -q 'meta'" % token + ) + + with subtest("rest_rejects_invalid_token"): + rest.fail( + "${pkgs.curl}/bin/curl -fsS " + "-H X-SLURM-USER-TOKEN:not-a-real-token " + "http://localhost:6820/slurm/v0.0.43/diag" + ) ''; } diff --git a/nixos/tests/systemd-journal-upload.nix b/nixos/tests/systemd-journal-upload.nix index 00dbba82b88b..af7817bd97d8 100644 --- a/nixos/tests/systemd-journal-upload.nix +++ b/nixos/tests/systemd-journal-upload.nix @@ -72,7 +72,7 @@ server.wait_for_unit("multi-user.target") client.wait_for_unit("multi-user.target") - def copy_pems(machine: Machine, domain: str): + def copy_pems(machine: BaseMachine, domain: str): machine.succeed("mkdir /run/secrets") machine.copy_from_host( source=f"{tmpdir}/{domain}/cert.pem", diff --git a/pkgs/by-name/bi/bind/package.nix b/pkgs/by-name/bi/bind/package.nix index c49ae029d7fa..0c4a7dd86461 100644 --- a/pkgs/by-name/bi/bind/package.nix +++ b/pkgs/by-name/bi/bind/package.nix @@ -29,11 +29,11 @@ stdenv.mkDerivation (finalAttrs: { pname = "bind"; - version = "9.20.18"; + version = "9.20.21"; src = fetchurl { url = "https://downloads.isc.org/isc/bind9/${finalAttrs.version}/bind-${finalAttrs.version}.tar.xz"; - hash = "sha256-38VGyZCsRRVSnNRcTdmVhisYrootDLKSCOiJal0yUzE="; + hash = "sha256-FeG1oifSiQ98ToI6bqAY3nDuLzoOhZy/89gqrYWQ3gM="; }; outputs = [ diff --git a/pkgs/by-name/li/libiscsi/package.nix b/pkgs/by-name/li/libiscsi/package.nix index 7b8f5d74aea0..b6d93ee53bb2 100644 --- a/pkgs/by-name/li/libiscsi/package.nix +++ b/pkgs/by-name/li/libiscsi/package.nix @@ -7,13 +7,13 @@ stdenv.mkDerivation (finalAttrs: { pname = "libiscsi"; - version = "1.20.0"; + version = "1.20.3"; src = fetchFromGitHub { owner = "sahlberg"; repo = "libiscsi"; rev = finalAttrs.version; - sha256 = "sha256-idiK9JowKhGAk5F5qJ57X14Q2Y0TbIKRI02onzLPkas="; + sha256 = "sha256-ARajWZ5/LIfFNCdp3HvQiyhR455+sJNzUPbBrz/pZ7E="; }; postPatch = '' @@ -24,8 +24,14 @@ stdenv.mkDerivation (finalAttrs: { nativeBuildInputs = [ autoreconfHook ]; env = lib.optionalAttrs (stdenv.hostPlatform.is32bit || stdenv.hostPlatform.isDarwin) { - # iscsi-discard.c:223:57: error: format specifies type 'unsigned long' but the argument has type 'uint64_t' (aka 'unsigned long long') [-Werror,-Wformat] - NIX_CFLAGS_COMPILE = "-Wno-error=format"; + NIX_CFLAGS_COMPILE = toString [ + # iscsi-discard.c:223:57: error: format specifies type 'unsigned long' but the argument has type 'uint64_t' (aka 'unsigned long long') [-Werror,-Wformat] + "-Wno-error=format" + # multithreading.c:257:16: error: 'sem_init' is deprecated [-Werror,-Wdeprecated-declarations] + "-Wno-error=deprecated-declarations" + # scsi-lowlevel.c:1244:11: error: cast from 'uint8_t *' (aka 'unsigned char *') to 'uint16_t *' (aka 'unsigned short *') increases required alignment from 1 to 2 [-Werror,-Wcast-align] + "-Wno-error=cast-align" + ]; }; meta = { diff --git a/pkgs/by-name/lo/lowdown/package.nix b/pkgs/by-name/lo/lowdown/package.nix index d74416a0cba6..ad56e0e6991d 100644 --- a/pkgs/by-name/lo/lowdown/package.nix +++ b/pkgs/by-name/lo/lowdown/package.nix @@ -11,6 +11,7 @@ enableDarwinSandbox ? true, # for passthru.tests nix, + lix, lowdown-unsandboxed, }: @@ -18,7 +19,7 @@ stdenv.mkDerivation rec { pname = "lowdown${ lib.optionalString (stdenv.hostPlatform.isDarwin && !enableDarwinSandbox) "-unsandboxed" }"; - version = "2.0.4"; + version = "3.0.1"; outputs = [ "out" @@ -29,7 +30,7 @@ stdenv.mkDerivation rec { src = fetchurl { url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${version}.tar.gz"; - sha512 = "649a508b7727df6e7e1203abb3853e05f167b64832fd5e1271f142ccf782e600b1de73c72dc02673d7b175effdc54f2c0f60318208a968af9f9763d09cf4f9ef"; + sha512 = "fe68e1b7ff23f3992398356d7aa9a330dfd7b72e22bea9a91eeef74182b209ecea0c9f3e2b2216e1a07b2358da2b746238ec9cbbdeebdd3551cef14dd2d79f46"; }; # https://github.com/kristapsdz/lowdown/pull/171 @@ -42,12 +43,6 @@ stdenv.mkDerivation rec { ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ fixDarwinDylibNames ]; - postPatch = '' - # fails test, some column width mismatch - rm regress/table-footnotes.md - rm regress/table-styles.md - ''; - # The Darwin sandbox calls fail inside Nix builds, presumably due to # being nested inside another sandbox. preConfigure = lib.optionalString (stdenv.hostPlatform.isDarwin && !enableDarwinSandbox) '' @@ -103,7 +98,7 @@ stdenv.mkDerivation rec { runHook preInstallCheck echo '# TEST' > test.md - $out/bin/lowdown test.md + $out/bin/lowdown test.md | grep '[hH]1' runHook postInstallCheck ''; @@ -113,7 +108,7 @@ stdenv.mkDerivation rec { passthru.tests = { # most important consumers in nixpkgs - inherit nix lowdown-unsandboxed; + inherit nix lix lowdown-unsandboxed; }; meta = { 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 95ebb3c9c0c2..0e89656ec7aa 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 @@ -717,6 +717,12 @@ def switch_to_configuration( }, remote=target_host, sudo=sudo, + # switch-to-configuration is not expected to produce meaningful + # stdout, but if it (or any of its children) does, it would leak + # into our stdout and break the "only the store path on stdout" + # contract documented in services.py (see print_result). Redirect + # its stdout to our stderr defensively. + stdout=sys.stderr, ) 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 456a41f46eac..3ebba4edc5b6 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 @@ -249,6 +249,7 @@ def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None: "boot", ], check=True, + stdout=ANY, **( DEFAULT_RUN_KWARGS | { @@ -547,6 +548,7 @@ def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None: "switch", ], check=True, + stdout=ANY, **DEFAULT_RUN_KWARGS, ), ] @@ -795,6 +797,7 @@ def test_execute_nix_switch_build_target_host( "switch", ], check=True, + stdout=ANY, **DEFAULT_RUN_KWARGS, ), ] @@ -920,6 +923,7 @@ def test_execute_nix_switch_flake_target_host( "switch", ], check=True, + stdout=ANY, **DEFAULT_RUN_KWARGS, ), ] @@ -1047,6 +1051,7 @@ def test_execute_nix_switch_flake_build_host( "switch", ], check=True, + stdout=ANY, **DEFAULT_RUN_KWARGS, ), ] @@ -1132,6 +1137,7 @@ def test_execute_switch_rollback(mock_run: Mock, tmp_path: Path) -> None: "switch", ], check=True, + stdout=ANY, **DEFAULT_RUN_KWARGS, ), ] @@ -1307,6 +1313,7 @@ def test_execute_test_flake(mock_run: Mock, tmp_path: Path) -> None: call( [config_path / "bin/switch-to-configuration", "test"], check=True, + stdout=ANY, **DEFAULT_RUN_KWARGS, ), ] @@ -1372,6 +1379,7 @@ def test_execute_test_rollback( "test", ], check=True, + stdout=ANY, **DEFAULT_RUN_KWARGS, ), ] @@ -1433,6 +1441,7 @@ def test_execute_switch_store_path(mock_run: Mock, tmp_path: Path) -> None: "switch", ], check=True, + stdout=ANY, **( DEFAULT_RUN_KWARGS | { @@ -1551,6 +1560,7 @@ def test_execute_switch_store_path_target_host( "switch", ], check=True, + stdout=ANY, **DEFAULT_RUN_KWARGS, ), ] diff --git a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py index 28948d1eaaff..f8c6d75c5290 100644 --- a/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py +++ b/pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py @@ -770,6 +770,7 @@ def test_switch_to_configuration_without_systemd_run( }, sudo=False, remote=None, + stdout=sys.stderr, ) with pytest.raises(m.NixOSRebuildError) as e: @@ -811,6 +812,7 @@ def test_switch_to_configuration_without_systemd_run( }, sudo=True, remote=target_host, + stdout=sys.stderr, ) @@ -846,6 +848,7 @@ def test_switch_to_configuration_with_systemd_run( }, sudo=False, remote=None, + stdout=sys.stderr, ) target_host = m.Remote("user@localhost", [], None, "ssh") @@ -875,6 +878,7 @@ def test_switch_to_configuration_with_systemd_run( }, sudo=True, remote=target_host, + stdout=sys.stderr, ) diff --git a/pkgs/by-name/ru/ruff/package.nix b/pkgs/by-name/ru/ruff/package.nix index 4eb1a03897a4..fbb2d76bb659 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.15.7"; + version = "0.15.8"; src = fetchFromGitHub { owner = "astral-sh"; repo = "ruff"; tag = finalAttrs.version; - hash = "sha256-aDRFNJKvxuHPYaZtoM+93DxJGsTPMLKGBH5QhIiTh0Y="; + hash = "sha256-hjllQ7lw2hecrwhPQlcq97LLxFYR28vCY14Ty6eBox4="; }; cargoBuildFlags = [ "--package=ruff" ]; - cargoHash = "sha256-m3VkHXhjemXVOFFVSUOVz0xD2Rc2pMsP+dnMYQD29uI="; + cargoHash = "sha256-iQKX+TqfP9r5P+UJKLqNysHYJm8N6qCCDfs8KYSb1vY="; nativeBuildInputs = [ installShellFiles ]; diff --git a/pkgs/by-name/sl/slurm/package.nix b/pkgs/by-name/sl/slurm/package.nix index 974657f170e0..ea06a5f93d88 100644 --- a/pkgs/by-name/sl/slurm/package.nix +++ b/pkgs/by-name/sl/slurm/package.nix @@ -34,6 +34,7 @@ http-parser, # enable internal X11 support via libssh2 enableX11 ? true, + enablePAM ? true, enableNVML ? config.cudaSupport, cudaPackages, symlinkJoin, @@ -144,17 +145,36 @@ stdenv.mkDerivation (finalAttrs: { "--without-rpath" # Required for configure to pick up the right dlopen path ] ++ (lib.optional (!enableX11) "--disable-x11") - ++ (lib.optional enableNVML "--with-nvml"); + ++ (lib.optional enableNVML "--with-nvml") + ++ (lib.optional enablePAM "--enable-pam --with-pam_dir=${placeholder "out"}/lib/security"); preConfigure = '' patchShebangs ./doc/html/shtml2html.py patchShebangs ./doc/man/man2html.py + '' + + (lib.optionalString enablePAM '' + mkdir -p $out/lib/security + ''); + postConfigure = lib.optionalString enablePAM '' + rm -rf $out ''; - postInstall = '' - rm -f $out/lib/*.la $out/lib/slurm/*.la + postBuild = lib.optionalString enablePAM '' + make -C contribs/pam + make -C contribs/pam_slurm_adopt ''; + postInstall = + (lib.optionalString enablePAM '' + export LIBRARY_PATH="$PWD/src/api/.libs:''${LIBRARY_PATH:+:$LIBRARY_PATH}" + mkdir -p $out/lib/security + make -C contribs/pam install + make -C contribs/pam_slurm_adopt install + '') + + '' + rm -f $out/lib/*.la $out/lib/slurm/*.la $out/lib/security/*.la + ''; + enableParallelBuilding = true; passthru.tests.slurm = nixosTests.slurm; @@ -166,6 +186,7 @@ stdenv.mkDerivation (finalAttrs: { license = lib.licenses.gpl2Only; maintainers = with lib.maintainers; [ markuskowa + edwtjo ]; }; }) diff --git a/pkgs/by-name/wo/wolfssl/package.nix b/pkgs/by-name/wo/wolfssl/package.nix index e904c4dfefcc..52c0d752dc0a 100644 --- a/pkgs/by-name/wo/wolfssl/package.nix +++ b/pkgs/by-name/wo/wolfssl/package.nix @@ -17,13 +17,13 @@ }: stdenv.mkDerivation (finalAttrs: { pname = "wolfssl-${variant}"; - version = "5.8.4"; + version = "5.9.0"; src = fetchFromGitHub { owner = "wolfSSL"; repo = "wolfssl"; tag = "v${finalAttrs.version}-stable"; - hash = "sha256-vfJKmDdM0r591t5GnuSS7NyiUYXCQOTKbWLVydB3N9s="; + hash = "sha256-Ov59Zt0UskADQThdzr9wni2junSpy3jiABWpiGr8xtg="; }; postPatch = '' diff --git a/pkgs/os-specific/linux/busybox/build-system-buffer-overflow.patch b/pkgs/os-specific/linux/busybox/build-system-buffer-overflow.patch new file mode 100644 index 000000000000..7fdb83a1596d --- /dev/null +++ b/pkgs/os-specific/linux/busybox/build-system-buffer-overflow.patch @@ -0,0 +1,27 @@ +From 3cf1ca7491bd5b6680e80355d76442ae14db681e Mon Sep 17 00:00:00 2001 +From: Alyssa Ross +Date: Sun, 29 Mar 2026 13:18:09 +0200 +Subject: [PATCH] build system: fix potential buffer overflow + +This could potentially write one byte past the end of line. +Identified by fortify-headers. +--- + scripts/basic/split-include.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/scripts/basic/split-include.c b/scripts/basic/split-include.c +index 6ef29195e..93011d511 100644 +--- a/scripts/basic/split-include.c ++++ b/scripts/basic/split-include.c +@@ -195,7 +195,7 @@ int main(int argc, const char * argv []) + ERROR_EXIT( "find" ); + + line[0] = '\n'; +- while (fgets(line+1, buffer_size, fp_find)) ++ while (fgets(line+1, buffer_size-1, fp_find)) + { + if (strstr(list_target, line) == NULL) + { +-- +2.53.0 + diff --git a/pkgs/os-specific/linux/busybox/default.nix b/pkgs/os-specific/linux/busybox/default.nix index 8402337be16d..677b884d1c6d 100644 --- a/pkgs/os-specific/linux/busybox/default.nix +++ b/pkgs/os-specific/linux/busybox/default.nix @@ -90,6 +90,8 @@ stdenv.mkDerivation rec { excludes = [ "networking/httpd_ratelimit_cgi.c" ]; # New since release. hash = "sha256-Msm9sDZrVx7ofunnvnTS73SPKUUpR3Tv5xZ/wBd+rts="; }) + # https://lists.busybox.net/pipermail/busybox/2026-March/092010.html + ./build-system-buffer-overflow.patch ] ++ lib.optional (stdenv.hostPlatform != stdenv.buildPlatform) ./clang-cross.patch; diff --git a/pkgs/os-specific/linux/kernel/kernels-org.json b/pkgs/os-specific/linux/kernel/kernels-org.json index 8eaa7261baa0..adf23aea9dbc 100644 --- a/pkgs/os-specific/linux/kernel/kernels-org.json +++ b/pkgs/os-specific/linux/kernel/kernels-org.json @@ -1,7 +1,7 @@ { "testing": { - "version": "7.0-rc5", - "hash": "sha256:00q4scz3kyrbd8v23pjdzgmaz9scmxg10cqlfwrrd7xj0hxp3pah", + "version": "7.0-rc6", + "hash": "sha256:085bc06mbav64rmsm6a34j2y75nrqpqp4qd8ld8mb9acf1i4iw45", "lts": false }, "6.1": { @@ -20,23 +20,23 @@ "lts": true }, "6.6": { - "version": "6.6.130", - "hash": "sha256:139480lyi3if8pd2j3yld5a01lk7113kbcn2kxpzyk29p5kslq14", + "version": "6.6.132", + "hash": "sha256:1d1fdd5wpphlm68yb16csaaijv0lf38ynl3gpvvdspsg22xjpdn5", "lts": true }, "6.12": { - "version": "6.12.78", - "hash": "sha256:0gdgykr4nqk1dzb5ms2m07saxx58zvacpfv8ynhfrv7snjs835vi", + "version": "6.12.80", + "hash": "sha256:0lrylj87bb8ky29pbplpncrfhmgqqbq3d49iqgdwv7p7jvc929f9", "lts": true }, "6.18": { - "version": "6.18.20", - "hash": "sha256:0lrm76rdlr92kjq3g410qdff9v49mpdf400lmsh7hq74k2ymlyl3", + "version": "6.18.21", + "hash": "sha256:0ks735y6jq4yy3jaicjsj4dn4n3kk2skf9dqh9dyifipn57j2f0w", "lts": true }, "6.19": { - "version": "6.19.10", - "hash": "sha256:072s76238rnf87yhdy15nbxfyq7x3ch7p2v14dq4pq551qd48va6", + "version": "6.19.11", + "hash": "sha256:16ymkc5r3hw05z7l7ih3qw406qlszz1l7b4g5yz0hv15ddxrs0r0", "lts": false } } diff --git a/pkgs/tools/package-management/lix/default.nix b/pkgs/tools/package-management/lix/default.nix index 1d95ef9a66ee..8ebcd187f4a3 100644 --- a/pkgs/tools/package-management/lix/default.nix +++ b/pkgs/tools/package-management/lix/default.nix @@ -41,6 +41,13 @@ let hash = "sha256-uu/SIG8fgVVWhsGxmszTPHwe4SQtLgbxdShOMKbeg2w="; }; + lixLowdown30Patch = fetchpatch { + name = "lix-lowdown-3.0-support.patch"; + url = "https://git.lix.systems/lix-project/lix/commit/af0390c27bdc401ece8f8192cb3024f0ff08e977.patch"; + excludes = [ "flake.nix" ]; + hash = "sha256-ZBkbgeZ/D7H2teX8bPy5NEG1aXbQVksTDV3aVBZdRPM="; + }; + makeLixScope = { attrName, @@ -229,6 +236,8 @@ lib.makeExtensible ( }) lixMdbookPatch + + lixLowdown30Patch ]; }; }; @@ -253,7 +262,10 @@ lib.makeExtensible ( hash = "sha256-APm8m6SVEAO17BBCka13u85/87Bj+LePP7Y3zHA3Mpg="; }; - patches = [ lixMdbookPatch ]; + patches = [ + lixMdbookPatch + lixLowdown30Patch + ]; }; }; @@ -283,14 +295,14 @@ lib.makeExtensible ( attrName = "git"; lix-args = rec { - version = "2.96.0-pre-20260317_${builtins.substring 0 12 src.rev}"; + version = "2.96.0-pre-20260318_${builtins.substring 0 12 src.rev}"; src = fetchFromGitea { domain = "git.lix.systems"; owner = "lix-project"; repo = "lix"; - rev = "96db7c79cf2a9a06725360b0d12e5de583bef07d"; - hash = "sha256-Ixwk38HArs7MZXxdWRkSZFzUhUdlCro+8+M/sO+fE/Y="; + rev = "8294cd534b2f01ee967b28aa73fcab1535d62b3d"; + hash = "sha256-BFijbNDCrfzpDdW+gNauP25QsTvEZ39dygWEI/RYeyY="; }; cargoDeps = rustPlatform.fetchCargoVendor { diff --git a/pkgs/tools/package-management/nix/default.nix b/pkgs/tools/package-management/nix/default.nix index 5783c99771b6..bbb45c9d419d 100644 --- a/pkgs/tools/package-management/nix/default.nix +++ b/pkgs/tools/package-management/nix/default.nix @@ -134,6 +134,21 @@ let patches_common = lib.optional ( stdenv.system == "aarch64-darwin" ) ./patches/skip-flaky-darwin-tests.patch; + + # Lowdown 3.0 compatibility patch for nix 2.31–2.33; fetched from the + # upstream backport (same diff on every maintenance branch after + # fetchpatch strips metadata). Nix 2.34.4+ and the git snapshot + # already include the fix in their tagged source. + lowdown30Patch = pkgs.fetchpatch { + name = "nix-lowdown-3.0-support.patch"; + url = "https://github.com/NixOS/nix/commit/472c35c561bd9e8db1465e0677f1efe2cb88c568.patch"; + hash = "sha256-ZCQgI/euBN8t9rgdCsGRgrcEWG3T5MUc+bQc4tIcHuI="; + }; + + # Lowdown 3.0 compatibility patch for nix 2.28 and 2.30, which have a + # different markdown.cc layout (no LOWDOWN_TERM_NORELLINK branch) and + # never received an upstream backport. + lowdown30PatchOld = ./patches/lowdown-3.0-compat-2.28-2.30.patch; in lib.makeExtensible ( self: @@ -149,6 +164,7 @@ lib.makeExtensible ( url = "https://github.com/NixOS/nix/commit/5a64138e862fe364e751c5c286e8db8c466aaee7.patch?full_index=1"; hash = "sha256-vFv/D08x9urtoIE9wiC7Lln4Eq3sgNBwU7TBE1iyrfI="; }) + lowdown30PatchOld ]; }; @@ -172,22 +188,27 @@ lib.makeExtensible ( url = "https://github.com/NixOS/nix/commit/5cbd7856de0a9c13351f98e32a1e26d0854d87fd.patch?full_index=1"; hash = "sha256-r2ZF1zBZDKMvyX6X4VsaTMrg0zdjn59Jf6Hqg56r29E="; }) + lowdown30PatchOld ] ); nix_2_30 = addTests "nix_2_30" self.nixComponents_2_30.nix-everything; - nixComponents_2_31 = nixDependencies.callPackage ./modular/packages.nix rec { - version = "2.31.3"; - inherit teams; - otherSplices = generateSplicesForNixComponents "nixComponents_2_31"; - src = fetchFromGitHub { - owner = "NixOS"; - repo = "nix"; - tag = version; - hash = "sha256-oe0YWe8f+pwQH4aYD2XXLW5iEHyXNUddurqJ5CUVCIk="; - }; - }; + nixComponents_2_31 = + (nixDependencies.callPackage ./modular/packages.nix rec { + version = "2.31.3"; + inherit teams; + otherSplices = generateSplicesForNixComponents "nixComponents_2_31"; + src = fetchFromGitHub { + owner = "NixOS"; + repo = "nix"; + tag = version; + hash = "sha256-oe0YWe8f+pwQH4aYD2XXLW5iEHyXNUddurqJ5CUVCIk="; + }; + }).appendPatches + [ + lowdown30Patch + ]; nix_2_31 = addTests "nix_2_31" self.nixComponents_2_31.nix-everything; diff --git a/pkgs/tools/package-management/nix/patches/lowdown-3.0-compat-2.28-2.30.patch b/pkgs/tools/package-management/nix/patches/lowdown-3.0-compat-2.28-2.30.patch new file mode 100644 index 000000000000..dd527f7f1cd6 --- /dev/null +++ b/pkgs/tools/package-management/nix/patches/lowdown-3.0-compat-2.28-2.30.patch @@ -0,0 +1,27 @@ +--- a/src/libcmd/markdown.cc ++++ b/src/libcmd/markdown.cc +@@ -37,7 +37,13 @@ + .vmargin = 0, + # endif + .feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES, +- .oflags = LOWDOWN_TERM_NOLINK, ++ .oflags = ++# if HAVE_LOWDOWN_3 ++ LOWDOWN_NOLINK ++# else ++ LOWDOWN_TERM_NOLINK ++# endif ++ , + }; + + auto doc = lowdown_doc_new(&opts); +--- a/src/libcmd/meson.build ++++ b/src/libcmd/meson.build +@@ -36,6 +36,7 @@ + configdata.set('HAVE_LOWDOWN', lowdown.found().to_int()) + # The API changed slightly around terminal initialization. + configdata.set('HAVE_LOWDOWN_1_4', lowdown.version().version_compare('>= 1.4.0').to_int()) ++configdata.set('HAVE_LOWDOWN_3', lowdown.version().version_compare('>= 3.0.0').to_int()) + + readline_flavor = get_option('readline-flavor') + if readline_flavor == 'editline'