staging-nixos merge for 2026-04-02 (#506019)
This commit is contained in:
@@ -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.
|
||||
:::
|
||||
|
||||
@@ -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-<interface>.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):
|
||||
|
||||
@@ -223,6 +223,13 @@ See <https://github.com/NixOS/nixpkgs/issues/481673>.
|
||||
|
||||
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`
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <<EOF
|
||||
${optionalString (cfg.nameservers != [ ] && cfg.domain != null) ''
|
||||
domain ${cfg.domain}
|
||||
''}
|
||||
${optionalString (cfg.search != [ ]) ("search " + concatStringsSep " " cfg.search)}
|
||||
${flip concatMapStrings cfg.nameservers (ns: ''
|
||||
nameserver ${ns}
|
||||
'')}
|
||||
EOF
|
||||
''}
|
||||
|
||||
# Set the default gateway
|
||||
${flip concatMapStrings
|
||||
[
|
||||
{
|
||||
version = "-4";
|
||||
gateway = cfg.defaultGateway;
|
||||
}
|
||||
{
|
||||
version = "-6";
|
||||
gateway = cfg.defaultGateway6;
|
||||
}
|
||||
]
|
||||
(
|
||||
{ version, gateway }:
|
||||
optionalString (gateway != null && gateway.address != "") ''
|
||||
${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;
|
||||
}
|
||||
}
|
||||
''
|
||||
)
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
# For each interface <foo>, create a job ‘network-addresses-<foo>.service"
|
||||
# that performs static address configuration. It has a "wants"
|
||||
# dependency on ‘<foo>.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 <foo>, creates a network-addresses-<foo>.service
|
||||
# job that performs static address configuration.
|
||||
#
|
||||
# It has a Wants dependency on <foo>-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"
|
||||
'';
|
||||
|
||||
@@ -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 <<EOF
|
||||
${optionalString (cfg.nameservers != [ ] && cfg.domain != null) ''
|
||||
domain ${cfg.domain}
|
||||
''}
|
||||
${optionalString (cfg.search != [ ]) ("search " + concatStringsSep " " cfg.search)}
|
||||
${flip concatMapStrings cfg.nameservers (ns: ''
|
||||
nameserver ${ns}
|
||||
'')}
|
||||
EOF
|
||||
'';
|
||||
|
||||
services.mstpd = mkIf needsMstpd { enable = true; };
|
||||
|
||||
virtualisation.vswitch = mkIf (cfg.vswitches != { }) { enable = true; };
|
||||
|
||||
@@ -1472,6 +1472,7 @@ in
|
||||
slimserver = runTest ./slimserver.nix;
|
||||
slipshow = runTest ./slipshow.nix;
|
||||
slurm = runTest ./slurm.nix;
|
||||
slurm-pam = runTest ./slurm-pam.nix;
|
||||
smokeping = runTest ./smokeping.nix;
|
||||
snapcast = runTest ./snapcast.nix;
|
||||
snapper = runTest ./snapper.nix;
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
node_id: str
|
||||
host: str
|
||||
|
||||
def get_node_fqn(machine: Machine) -> 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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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=$(</proc/self/sessionid)"
|
||||
echo "export XDG_SESSION_TYPE=tty"
|
||||
echo "export XDG_SESSION_CLASS=user"
|
||||
''}
|
||||
SrunEpilog=${pkgs.writers.writeBash "slurm-epilog" ''
|
||||
#Only disable linger if this is the last job running for this user.
|
||||
O_P=0
|
||||
for pid in $(scontrol listpids | awk -v jid=$SLURM_JOB_ID 'NR!=1 { if ($2 != jid && $1 != "-1"){print $1} }'); do
|
||||
ps --noheader -o euser p $pid | grep -q $SLURM_JOB_USER && O_P=1
|
||||
done
|
||||
if [ $O_P -eq 0 ]; then
|
||||
loginctl disable-linger $SLURM_JOB_USER
|
||||
fi
|
||||
exit 0
|
||||
''}
|
||||
'';
|
||||
security.pam.services.sshd.slurm.adopt.enable = true;
|
||||
# disable `pam_systemd` on adopt node
|
||||
security.pam.services.sshd.startSession = lib.mkForce false;
|
||||
security.pam.services.sshd.slurm.adopt.settings.action_adopt_failure = "allow";
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
control =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [ slurmconfig ];
|
||||
services.slurm = {
|
||||
server.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
submit =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [ slurmconfig ];
|
||||
services.slurm = {
|
||||
enableStools = true;
|
||||
};
|
||||
};
|
||||
|
||||
regular = computeNode;
|
||||
pamslurm = computePAMNode;
|
||||
pamslurmadopt = computePAMAdoptNode;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
# Make sure cluster state is ready before testing PAM behavior.
|
||||
#
|
||||
# This shows that the controller is up, all compute nodes have registered,
|
||||
# and Slurm sees every test node as schedulable (`idle`). Without this,
|
||||
# later SSH failures would be ambiguous: they could come from PAM policy,
|
||||
# missing node registration, or startup races.
|
||||
with subtest("cluster_is_ready"):
|
||||
control.wait_for_unit("slurmctld.service")
|
||||
for node in [regular, pamslurm, pamslurmadopt]:
|
||||
node.wait_for_unit("slurmd")
|
||||
submit.wait_for_unit("multi-user.target")
|
||||
submit.wait_until_succeeds(
|
||||
"sinfo -Nh -o '%N %T' | grep -Fx 'regular idle' && "
|
||||
"sinfo -Nh -o '%N %T' | grep -Fx 'pamslurm idle' && "
|
||||
"sinfo -Nh -o '%N %T' | grep -Fx 'pamslurmadopt idle'"
|
||||
)
|
||||
|
||||
# Sanity-check the Slurm cluster itself before layering PAM expectations on
|
||||
# top of it. This ensures the scheduler can place a job across all three
|
||||
# nodes, so later PAM-specific failures are not caused by a broken cluster.
|
||||
with subtest("run_distributed_command"):
|
||||
submit.succeed("srun -N 3 hostname | sort | uniq | wc -l | xargs test 3 -eq")
|
||||
|
||||
# Verify that the generated PAM stacks match the intended integration:
|
||||
#
|
||||
# - `pamslurm` should include the legacy `pam_slurm.so`.
|
||||
# - `pamslurmadopt` should include `pam_slurm_adopt.so`.
|
||||
# - `pam_slurm_adopt.so` must be the last account module.
|
||||
# - `pam_systemd.so` must be absent because it conflicts with
|
||||
# `pam_slurm_adopt`.
|
||||
#
|
||||
# This hopefully catches regressions before trying any live login.
|
||||
with subtest("pam_files_are_as_expected"):
|
||||
# `pam_systemd` conflicts with `pam_slurm_adopt`
|
||||
pamslurm.succeed("grep -q '${pkgs.slurm}/lib/security/pam_slurm.so' /etc/pam.d/sshd")
|
||||
pamslurmadopt.succeed("grep -q '${pkgs.slurm}/lib/security/pam_slurm_adopt.so' /etc/pam.d/sshd")
|
||||
pamslurmadopt.succeed(
|
||||
"awk '$1 == \"account\" { print $3 }' /etc/pam.d/sshd | tail -n1 | "
|
||||
"grep -Fx '${pkgs.slurm}/lib/security/pam_slurm_adopt.so'"
|
||||
)
|
||||
pamslurmadopt.fail("grep -q pam_systemd.so /etc/pam.d/sshd")
|
||||
|
||||
# Install the test SSH private key on the submit host once so all later SSH
|
||||
# checks use the same credential and client configuration.
|
||||
with subtest("prepare_ssh_key"):
|
||||
submit.succeed(
|
||||
"install -m 0600 ${snakeOilPrivateKey} /root/privkey.snakeoil"
|
||||
)
|
||||
|
||||
# Regression check: a normal node with no Slurm PAM restrictions
|
||||
# must still allow ordinary public-key SSH login for the test user.
|
||||
with subtest("regular_node_allows_pubkey_ssh"):
|
||||
submit.succeed(
|
||||
"ssh ${sshOpts} submitter@regular true",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# Regression check: ordinary password SSH must also still work on
|
||||
# the regular node. This guards against accidentally breaking plain
|
||||
# `pam_unix` behavior while adding Slurm-specific PAM hooks elsewhere.
|
||||
with subtest("regular_node_allows_password_ssh"):
|
||||
submit.succeed(
|
||||
"sshpass -p submitter ssh ${sshPassOpts} submitter@regular true",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# Negative companion to the previous test: wrong passwords must still be
|
||||
# rejected on the regular node, proving that the password path is actually
|
||||
# authenticating and not silently bypassed.
|
||||
with subtest("regular_node_rejects_wrong_password_ssh"):
|
||||
submit.fail(
|
||||
"sshpass -p wrong-password ssh ${sshPassOpts} submitter@regular true",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# Direct PAM regression check on the regular node. This bypasses SSH and
|
||||
# exercises the local PAM service stack itself, confirming that correct
|
||||
# `pam_unix` authentication still succeeds.
|
||||
with subtest("regular_node_pam_unix_accepts_correct_password"):
|
||||
regular.succeed(
|
||||
"printf 'submitter\\n' | pamtester login submitter authenticate acct_mgmt"
|
||||
)
|
||||
|
||||
# Negative companion to the previous test: wrong passwords must still be
|
||||
# rejected on the regular node, proving that the password path is actually
|
||||
# authenticating and not silently bypassed.
|
||||
with subtest("regular_node_pam_unix_rejects_wrong_password"):
|
||||
regular.fail(
|
||||
"printf 'wrong-password\\n' | pamtester login submitter authenticate"
|
||||
)
|
||||
|
||||
# Core policy check for both Slurm-protected nodes: without any active job
|
||||
# owned by the user on the target node, SSH must be denied.
|
||||
#
|
||||
# This is the main negative regression test for the Slurm PAM integration.
|
||||
with subtest("deny_ssh_without_job"):
|
||||
submit.fail(
|
||||
"ssh ${sshOpts} submitter@pamslurm true",
|
||||
timeout=30
|
||||
)
|
||||
submit.fail(
|
||||
"ssh ${sshOpts} submitter@pamslurmadopt true",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# Verify node-local visibility for the legacy `pam_slurm` integration.
|
||||
#
|
||||
# We start a long-running allocation specifically on `pamslurm` as the
|
||||
# `submitter` user, then confirm Slurm reports that exact node/user/job
|
||||
# combination. The important negative assertion is that this job must NOT
|
||||
# grant access to a different node (`pamslurmadopt`).
|
||||
#
|
||||
# We intentionally keep this as a visibility/isolation test rather than a
|
||||
# positive SSH-success test, since `pam_slurm` module is less
|
||||
# deterministicly to assert positive here than `pam_slurm_adopt`.
|
||||
with subtest("pam_slurm_job_visibility_is_node_local"):
|
||||
submit.succeed(
|
||||
"runuser -u submitter -- sh -lc "
|
||||
"\"srun --job-name=wait-pamslurm --nodelist=pamslurm sleep 300 "
|
||||
">/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
|
||||
)
|
||||
'';
|
||||
}
|
||||
+64
-16
@@ -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"
|
||||
)
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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 ];
|
||||
|
||||
|
||||
@@ -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
|
||||
];
|
||||
};
|
||||
})
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
From 3cf1ca7491bd5b6680e80355d76442ae14db681e Mon Sep 17 00:00:00 2001
|
||||
From: Alyssa Ross <hi@alyssa.is>
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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'
|
||||
Reference in New Issue
Block a user