staging-nixos merge for 2026-05-14 (#520129)
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):
|
||||
|
||||
@@ -315,6 +315,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`
|
||||
|
||||
@@ -23,10 +23,12 @@ let
|
||||
'';
|
||||
|
||||
# Sadly, systemd-vconsole-setup doesn't support binary keymaps.
|
||||
vconsoleConf = pkgs.writeText "vconsole.conf" ''
|
||||
KEYMAP=${cfg.keyMap}
|
||||
${lib.optionalString (cfg.font != null) "FONT=${cfg.font}"}
|
||||
'';
|
||||
vconsoleConf =
|
||||
withFont:
|
||||
pkgs.writeText "vconsole.conf" ''
|
||||
KEYMAP=${cfg.keyMap}
|
||||
${lib.optionalString (withFont && cfg.font != null) "FONT=${cfg.font}"}
|
||||
'';
|
||||
|
||||
consoleEnv =
|
||||
kbd:
|
||||
@@ -163,7 +165,7 @@ in
|
||||
|
||||
# Let systemd-vconsole-setup.service do the work of setting up the
|
||||
# virtual consoles.
|
||||
environment.etc."vconsole.conf".source = vconsoleConf;
|
||||
environment.etc."vconsole.conf".source = vconsoleConf true;
|
||||
# Provide kbd with additional packages.
|
||||
environment.etc.kbd.source = "${consoleEnv pkgs.kbd}/share";
|
||||
|
||||
@@ -180,7 +182,7 @@ in
|
||||
);
|
||||
|
||||
boot.initrd.systemd.contents = {
|
||||
"/etc/vconsole.conf".source = vconsoleConf;
|
||||
"/etc/vconsole.conf".source = vconsoleConf cfg.earlySetup;
|
||||
# Add everything if we want full console setup...
|
||||
"/etc/kbd" = lib.mkIf cfg.earlySetup {
|
||||
source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share";
|
||||
@@ -189,9 +191,6 @@ in
|
||||
"/etc/kbd/keymaps" = lib.mkIf (!cfg.earlySetup) {
|
||||
source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share/keymaps";
|
||||
};
|
||||
"/etc/kbd/consolefonts" = lib.mkIf (!cfg.earlySetup && cfg.font != null) {
|
||||
source = "${consoleEnv config.boot.initrd.systemd.package.kbd}/share/consolefonts";
|
||||
};
|
||||
};
|
||||
boot.initrd.systemd.additionalUpstreamUnits = [
|
||||
"systemd-vconsole-setup.service"
|
||||
@@ -201,7 +200,7 @@ in
|
||||
"${config.boot.initrd.systemd.package.kbd}/bin/setfont"
|
||||
"${config.boot.initrd.systemd.package.kbd}/bin/loadkeys"
|
||||
]
|
||||
++ lib.optionals (cfg.font != null && lib.hasPrefix builtins.storeDir cfg.font) [
|
||||
++ lib.optionals (cfg.font != null && cfg.earlySetup && lib.hasPrefix builtins.storeDir cfg.font) [
|
||||
"${cfg.font}"
|
||||
]
|
||||
++ lib.optionals (lib.hasPrefix builtins.storeDir cfg.keyMap) [
|
||||
@@ -216,7 +215,7 @@ in
|
||||
description = "Reset console on configuration changes";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [
|
||||
vconsoleConf
|
||||
(config.environment.etc."vconsole.conf".source)
|
||||
(consoleEnv pkgs.kbd)
|
||||
];
|
||||
reloadIfChanged = true;
|
||||
|
||||
@@ -76,7 +76,7 @@ let
|
||||
{
|
||||
inputs = {
|
||||
# This is pointing to an unstable release.
|
||||
# If you prefer a stable release instead, you can this to the latest number shown here: https://nixos.org/download
|
||||
# If you prefer a stable release instead, you can change the word unstable to the latest number shown here: https://nixos.org/download
|
||||
# i.e. nixos-24.11
|
||||
# Use `nix flake update` to update the flake to the latest revision of the chosen release channel.
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
@@ -143,6 +143,8 @@ let
|
||||
"final.target"
|
||||
"kexec.target"
|
||||
"systemd-kexec.service"
|
||||
"soft-reboot.target"
|
||||
"systemd-soft-reboot.service"
|
||||
]
|
||||
++ lib.optional cfg.package.withUtmp "systemd-update-utmp.service"
|
||||
++ [
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
@@ -51,6 +50,9 @@
|
||||
];
|
||||
|
||||
boot.initrd.systemd = {
|
||||
storePaths = lib.mkIf config.system.etc.overlay.mutable [
|
||||
"${config.system.nixos-init.package}/bin/clear-etc-opaque"
|
||||
];
|
||||
mounts = [
|
||||
{
|
||||
where = "/run/nixos-etc-metadata";
|
||||
@@ -131,13 +133,20 @@
|
||||
before = [ "initrd-fs.target" ];
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
RequiresMountsFor = "/sysroot";
|
||||
RequiresMountsFor = [
|
||||
"/sysroot"
|
||||
# Needed so we can clear stale opaque markers from the
|
||||
# upperdir based on the contents of the new metadata layer
|
||||
# before the overlay is mounted.
|
||||
"/run/nixos-etc-metadata"
|
||||
];
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = ''
|
||||
/bin/mkdir -p -m 0755 /sysroot/.rw-etc/upper /sysroot/.rw-etc/work
|
||||
'';
|
||||
ExecStart = [
|
||||
"/bin/mkdir -p -m 0755 /sysroot/.rw-etc/upper /sysroot/.rw-etc/work"
|
||||
"${config.system.nixos-init.package}/bin/clear-etc-opaque /run/nixos-etc-metadata /sysroot/.rw-etc/upper"
|
||||
];
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
@@ -285,6 +285,13 @@ in
|
||||
tmpMetadataMount=$(TMPDIR="/run" mktemp --directory -t nixos-etc-metadata.XXXXXXXXXX)
|
||||
mount --type erofs --options ro,nodev,nosuid ${config.system.build.etcMetadataImage} "$tmpMetadataMount"
|
||||
|
||||
${lib.optionalString config.system.etc.overlay.mutable ''
|
||||
# Clear stale opaque markers from the upperdir so that lowerdir
|
||||
# entries added by the new generation are not hidden.
|
||||
# See https://github.com/NixOS/nixpkgs/issues/505475
|
||||
${config.system.nixos-init.package}/bin/clear-etc-opaque "$tmpMetadataMount" /.rw-etc/upper
|
||||
''}
|
||||
|
||||
# There was no previous /etc mounted. This happens when we're called
|
||||
# directly without an initrd, like with nixos-enter.
|
||||
if ! mountpoint -q /etc; then
|
||||
|
||||
@@ -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,13 +867,39 @@ 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" ];
|
||||
};
|
||||
};
|
||||
|
||||
services.udev.extraRules = ''
|
||||
KERNEL=="tun", TAG+="systemd"
|
||||
'';
|
||||
# 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 = lib.concatStringsSep "\n" (
|
||||
[ ''KERNEL=="tun", TAG+="systemd"'' ]
|
||||
# This creates a udev rule to start each service with a WantedBy
|
||||
# dependency on a device unit. It's needed because if the service
|
||||
# unit is loaded in stage 2 but its device was already up by
|
||||
# stage 1, systemd will not automatically start it.
|
||||
++ lib.forEach (lib.attrNames cfg.interfaces) (
|
||||
iface:
|
||||
''ACTION=="add", SUBSYSTEM=="net", KERNEL=="${iface}", ''
|
||||
+ ''ENV{SYSTEMD_WANTS}="network-addresses-${iface}.service"''
|
||||
)
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -1853,6 +1852,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; };
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
|
||||
specialisation.new-generation.configuration = {
|
||||
environment.etc."newgen".text = "newgen";
|
||||
# Regression test for https://github.com/NixOS/nixpkgs/issues/505475:
|
||||
# A symlink in a subdirectory that does not exist in the base generation's
|
||||
# lowerdir. If something creates that subdirectory at runtime before
|
||||
# switching (e.g. stage-2-init.sh creating /etc/nixos), overlayfs makes it
|
||||
# opaque, hiding lowerdir content added by the new generation.
|
||||
environment.etc."nixos/newlink".source = pkgs.emptyDirectory;
|
||||
};
|
||||
specialisation.newer-generation.configuration = {
|
||||
environment.etc."newergen".text = "newergen";
|
||||
@@ -53,6 +59,13 @@
|
||||
machine.succeed("stat --format '%F' /etc/modetest2 | tee /dev/stderr | grep -Eq '^regular file$'")
|
||||
machine.succeed("stat --format '%a' /etc/modetest2 | tee /dev/stderr | grep -Eq '^300$'")
|
||||
|
||||
with subtest("/etc/nixos created by stage-2-init is opaque in upperdir"):
|
||||
# stage-2-init.sh unconditionally runs `install -d /etc/nixos`. Since
|
||||
# /nixos is not in the lowerdir, overlayfs creates it as an opaque dir
|
||||
# in the upperdir. Verify this precondition for the regression test below.
|
||||
machine.succeed("test -d /.rw-etc/upper/nixos")
|
||||
print(machine.succeed("getfattr -h -d -m 'trusted.overlay' /.rw-etc/upper/nixos 2>&1 || true"))
|
||||
|
||||
with subtest("switching to the same generation"):
|
||||
machine.succeed("/run/current-system/bin/switch-to-configuration test")
|
||||
|
||||
@@ -77,6 +90,15 @@
|
||||
assert machine.succeed("cat /etc/newgen") == "newgen"
|
||||
assert machine.succeed("cat /etc/mutable") == "mutable"
|
||||
|
||||
# Regression test for https://github.com/NixOS/nixpkgs/issues/505475:
|
||||
# The opaque /etc/nixos in the upperdir (created by stage-2-init.sh
|
||||
# before /nixos existed in the lowerdir) must not hide lowerdir entries
|
||||
# added by the new generation. The activation script must have cleared
|
||||
# the stale opaque marker.
|
||||
print(machine.succeed("ls -la /etc/nixos/"))
|
||||
machine.succeed("test -L /etc/nixos/newlink")
|
||||
machine.fail("getfattr -h -n trusted.overlay.opaque /.rw-etc/upper/nixos")
|
||||
|
||||
print(machine.succeed("findmnt /etc/mountpoint"))
|
||||
print(machine.succeed("stat /etc/mountpoint/extra-file"))
|
||||
print(machine.succeed("findmnt /etc/filemount"))
|
||||
@@ -93,5 +115,23 @@
|
||||
numOfMetaMounts = len(metaMounts.splitlines())
|
||||
assert numOfTmpMounts == 0, f"Found {numOfTmpMounts} remaining tmpmounts"
|
||||
assert numOfMetaMounts == 1, f"Found {numOfMetaMounts} remaining metamounts"
|
||||
|
||||
with subtest("stale opaque markers are cleared by initrd on boot (NixOS/nixpkgs#505475)"):
|
||||
# Simulate the bug precondition: an opaque /pam.d in the upperdir.
|
||||
# /pam.d is guaranteed to exist as a directory in the metadata layer.
|
||||
machine.succeed("mkdir -p /.rw-etc/upper/pam.d")
|
||||
machine.succeed("setfattr -h -n trusted.overlay.opaque -v y /.rw-etc/upper/pam.d")
|
||||
machine.succeed("getfattr -h -n trusted.overlay.opaque /.rw-etc/upper/pam.d")
|
||||
# Also create a non-opaque upperdir directory that exists in the
|
||||
# metadata layer, to ensure clear-etc-opaque tolerates the
|
||||
# already-clear case.
|
||||
machine.succeed("mkdir -p /.rw-etc/upper/systemd")
|
||||
|
||||
# Reboot and verify the initrd rw-etc service cleared the opaque marker.
|
||||
machine.shutdown()
|
||||
machine.start()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.fail("getfattr -h -n trusted.overlay.opaque /.rw-etc/upper/pam.d")
|
||||
machine.succeed("test -e /etc/pam.d/login")
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Generated
+11
@@ -142,6 +142,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -506,3 +507,13 @@ checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
@@ -11,6 +11,7 @@ pathrs = "0.2.2"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
bootspec = "2.0.0"
|
||||
xattr = "1.6.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.20.0"
|
||||
|
||||
@@ -52,6 +52,9 @@ closure. Currently nixos-init comes in at ~500 KiB.
|
||||
- `find-etc`: Finds the `/etc` paths in `/sysroot` so that the initrd doesn't
|
||||
directly depend on the toplevel, reducing the need to rebuild the initrd on
|
||||
every generation.
|
||||
- `clear-etc-opaque`: Clears stale `trusted.overlay.opaque` xattrs from the
|
||||
mutable `/etc` overlay's upperdir before it is mounted, so that lowerdir
|
||||
entries added by a new generation are not hidden.
|
||||
- `resolve-in-root`: Figures out the canonical path inside a chroot.
|
||||
|
||||
## Future
|
||||
|
||||
@@ -47,6 +47,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
binaries = [
|
||||
"initrd-init"
|
||||
"find-etc"
|
||||
"clear-etc-opaque"
|
||||
"resolve-in-root"
|
||||
"env-generator"
|
||||
];
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
const OVERLAY_OPAQUE_XATTR: &str = "trusted.overlay.opaque";
|
||||
|
||||
/// Entrypoint for the `clear-etc-opaque` binary.
|
||||
///
|
||||
/// When a directory is created in the mutable `/etc` overlay that does not yet
|
||||
/// exist in the lowerdir, overlayfs marks it opaque in the upperdir. This is
|
||||
/// correct at creation time, but becomes stale when a later generation adds
|
||||
/// entries under that same directory to the metadata layer: the opaque marker
|
||||
/// hides them.
|
||||
///
|
||||
/// This walks the (newly mounted) metadata layer and removes
|
||||
/// `trusted.overlay.opaque` from any upperdir directory that now has a
|
||||
/// directory counterpart in the lowerdir, turning it back into a merged view.
|
||||
/// Files the user placed in the upperdir remain visible (upperdir wins
|
||||
/// per-entry) and individual whiteouts are preserved; only the blanket hiding
|
||||
/// of lowerdir content is undone.
|
||||
///
|
||||
/// See <https://github.com/NixOS/nixpkgs/issues/505475>.
|
||||
///
|
||||
/// Usage: `clear-etc-opaque <metadata-mount> <upperdir>`
|
||||
pub fn clear_etc_opaque() -> Result<()> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() != 3 {
|
||||
bail!("Usage: {} <metadata-mount> <upperdir>", args[0]);
|
||||
}
|
||||
|
||||
let metadata_mount = PathBuf::from(&args[1]);
|
||||
let upperdir = PathBuf::from(&args[2]);
|
||||
|
||||
if !upperdir.is_dir() {
|
||||
// Nothing to clear (e.g. first boot before the upperdir is created).
|
||||
log::info!(
|
||||
"Upperdir {} does not exist, nothing to clear.",
|
||||
upperdir.display()
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
clear_opaque_markers(&metadata_mount, &metadata_mount, &upperdir)
|
||||
}
|
||||
|
||||
/// Recursively walk `current` (a subtree of `metadata_root`) and clear the
|
||||
/// opaque xattr from the corresponding directory in `upperdir`.
|
||||
fn clear_opaque_markers(metadata_root: &Path, current: &Path, upperdir: &Path) -> Result<()> {
|
||||
let entries = fs::read_dir(current)
|
||||
.with_context(|| format!("Failed to read directory {}", current.display()))?;
|
||||
|
||||
for entry in entries {
|
||||
let entry =
|
||||
entry.with_context(|| format!("Failed to read entry in {}", current.display()))?;
|
||||
|
||||
// Use the entry's own type info (no symlink following) so we only
|
||||
// recurse into real directories of the metadata image.
|
||||
if !entry
|
||||
.file_type()
|
||||
.with_context(|| format!("Failed to stat {}", entry.path().display()))?
|
||||
.is_dir()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = entry.path();
|
||||
let rel = path
|
||||
.strip_prefix(metadata_root)
|
||||
.context("Failed to strip metadata root prefix")?;
|
||||
let target = upperdir.join(rel);
|
||||
|
||||
// Only act on real directories in the upperdir; an opaque marker on a
|
||||
// non-directory would be meaningless and we must not follow symlinks
|
||||
// out of the upperdir.
|
||||
match fs::symlink_metadata(&target) {
|
||||
Ok(meta) if meta.is_dir() => {
|
||||
remove_opaque_xattr(&target);
|
||||
// Only recurse when the upperdir also has this directory:
|
||||
// deeper lowerdir directories without an upperdir counterpart
|
||||
// cannot carry stale markers.
|
||||
clear_opaque_markers(metadata_root, &path, upperdir)?;
|
||||
}
|
||||
// Missing or not a directory: nothing to do for this subtree.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove the `trusted.overlay.opaque` xattr from `path` if present.
|
||||
fn remove_opaque_xattr(path: &Path) {
|
||||
// Check first instead of removing unconditionally: lremovexattr(2) reports
|
||||
// a missing attribute as ENODATA, which std does not map to a stable
|
||||
// io::ErrorKind, so distinguishing it from real errors is awkward.
|
||||
match xattr::get(path, OVERLAY_OPAQUE_XATTR) {
|
||||
Ok(None) => return,
|
||||
Ok(Some(_)) => {}
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"Failed to read {OVERLAY_OPAQUE_XATTR} on {}: {err}.",
|
||||
path.display()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
match xattr::remove(path, OVERLAY_OPAQUE_XATTR) {
|
||||
Ok(()) => {
|
||||
log::info!("Cleared stale opaque marker from {}.", path.display());
|
||||
}
|
||||
Err(err) => {
|
||||
// Don't abort the boot over this; the worst case is that some
|
||||
// declaratively-managed /etc entries stay hidden, which is what
|
||||
// would happen anyway without this fixup.
|
||||
log::warn!(
|
||||
"Failed to remove {OVERLAY_OPAQUE_XATTR} from {}: {err}.",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn clears_opaque_only_for_matching_dirs() -> Result<()> {
|
||||
if !xattr::SUPPORTED_PLATFORM {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let metadata = tempdir()?;
|
||||
let upper = tempdir()?;
|
||||
|
||||
// lowerdir gained nixos/sub in the new generation.
|
||||
fs::create_dir_all(metadata.path().join("nixos/sub"))?;
|
||||
// upperdir has an opaque nixos/ from before.
|
||||
fs::create_dir_all(upper.path().join("nixos"))?;
|
||||
// upperdir directory without a lowerdir counterpart: we only clear
|
||||
// markers where the lowerdir has a matching directory, so this one
|
||||
// must stay opaque.
|
||||
fs::create_dir_all(upper.path().join("only-upper"))?;
|
||||
|
||||
// The build sandbox usually lacks CAP_SYS_ADMIN, so trusted.* xattrs
|
||||
// cannot be set. Skip in that case rather than fail the build.
|
||||
if xattr::set(upper.path().join("nixos"), OVERLAY_OPAQUE_XATTR, b"y").is_err() {
|
||||
eprintln!("skipping: cannot set trusted.* xattrs in this environment");
|
||||
return Ok(());
|
||||
}
|
||||
xattr::set(upper.path().join("only-upper"), OVERLAY_OPAQUE_XATTR, b"y")?;
|
||||
|
||||
clear_opaque_markers(metadata.path(), metadata.path(), upper.path())?;
|
||||
|
||||
assert!(xattr::get(upper.path().join("nixos"), OVERLAY_OPAQUE_XATTR)?.is_none());
|
||||
assert_eq!(
|
||||
xattr::get(upper.path().join("only-upper"), OVERLAY_OPAQUE_XATTR)?.as_deref(),
|
||||
Some(b"y".as_slice())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod activate;
|
||||
mod config;
|
||||
mod env_generator;
|
||||
mod etc_overlay;
|
||||
mod find_etc;
|
||||
mod fs;
|
||||
mod init;
|
||||
@@ -16,6 +17,7 @@ use anyhow::{Context, Result, bail};
|
||||
pub use crate::{
|
||||
activate::activate,
|
||||
env_generator::env_generator,
|
||||
etc_overlay::clear_etc_opaque,
|
||||
find_etc::find_etc,
|
||||
init::init,
|
||||
initrd_init::initrd_init,
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{env, io::Write, process::ExitCode};
|
||||
|
||||
use log::Level;
|
||||
|
||||
use nixos_init::{env_generator, find_etc, initrd_init, resolve_in_root};
|
||||
use nixos_init::{clear_etc_opaque, env_generator, find_etc, initrd_init, resolve_in_root};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let arg0 = env::args()
|
||||
@@ -12,6 +12,7 @@ fn main() -> ExitCode {
|
||||
|
||||
setup_logger();
|
||||
let entrypoint = match arg0.as_str() {
|
||||
"clear-etc-opaque" => clear_etc_opaque,
|
||||
"find-etc" => find_etc,
|
||||
"resolve-in-root" => resolve_in_root,
|
||||
"initrd-init" => initrd_init,
|
||||
|
||||
@@ -25,18 +25,18 @@
|
||||
"lts": true
|
||||
},
|
||||
"6.12": {
|
||||
"version": "6.12.87",
|
||||
"hash": "sha256:0c4qidff0qs2x0mvba83cw3ksaz2af3xwabvc839xvsc9djaf4nc",
|
||||
"version": "6.12.88",
|
||||
"hash": "sha256:1s2x66j46gxw17j8hh5iws0l00aivmphp3mn8vgwbn8sj44gacjb",
|
||||
"lts": true
|
||||
},
|
||||
"6.18": {
|
||||
"version": "6.18.29",
|
||||
"hash": "sha256:0g584ak8p9nqxysn8d1qzxbp1asfd39hqy9np35gri9v3xdsfgn3",
|
||||
"version": "6.18.30",
|
||||
"hash": "sha256:1m5kvzky4g2jc8b09np8can0lasg1lwjfgdj41s3ymxgqdp8icx8",
|
||||
"lts": true
|
||||
},
|
||||
"7.0": {
|
||||
"version": "7.0.6",
|
||||
"hash": "sha256:08vm18wx6399phzgr3wz94yga3ab4fyca79445ygvbspm904996b",
|
||||
"version": "7.0.7",
|
||||
"hash": "sha256:1x2xnb7gpj0inxdc317zi71i0d98b7wq64s0yzk2vzxalf3gxqf8",
|
||||
"lts": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
lib,
|
||||
mkMesonExecutable,
|
||||
stdenv,
|
||||
|
||||
nix-store,
|
||||
nix-expr,
|
||||
@@ -12,6 +13,11 @@
|
||||
# Configuration Options
|
||||
|
||||
version,
|
||||
|
||||
# Whether to link against mimalloc for malloc override.
|
||||
# Significantly improves evaluation performance on allocation-heavy
|
||||
# workloads (~10-15% on large evaluations).
|
||||
withMimalloc ? !stdenv.hostPlatform.isWindows,
|
||||
}:
|
||||
|
||||
mkMesonExecutable (finalAttrs: {
|
||||
@@ -25,10 +31,11 @@ mkMesonExecutable (finalAttrs: {
|
||||
nix-expr
|
||||
nix-main
|
||||
nix-cmd
|
||||
mimalloc
|
||||
];
|
||||
]
|
||||
++ lib.optional ((lib.versionAtLeast version "2.35pre") && withMimalloc) mimalloc;
|
||||
|
||||
mesonFlags = [
|
||||
mesonFlags = lib.optionals (lib.versionAtLeast version "2.35pre") [
|
||||
(lib.mesonEnable "mimalloc" withMimalloc)
|
||||
];
|
||||
|
||||
meta = {
|
||||
|
||||
Reference in New Issue
Block a user