As part of my migration from 25.11 to 26.05, I replaced a boot.initrd.postResumeCommands script for wiping a btrfs subvolume (as part of an impermanence configuration) with a systemd service:
boot.initrd.systemd.services.impermanence = {
description = "wipe root subvolume";
serviceConfig.Type = "oneshot";
after = [ "initrd-root-device.target" ];
requires = [ "initrd-root-device.target" ];
before = [ "sysroot.mount" ];
wantedBy = [ "initrd.target" ];
path = with pkgs; [
btrfs-progs
coreutils
util-linux
];
script = ''
mkdir /btrfs_tmp
mount /dev/disk/by-label/NIX /btrfs_tmp
btrfs subvolume delete -R /btrfs_tmp/root
btrfs subvolume create /btrfs_tmp/root
umount /btrfs_tmp
rm -r /btrfs_tmp
'';
};
However, there is something wrong with the path as mount, which is provided by util-linux, is command not found.
It works if I use a hack:
environment.PATH = lib.mkForce "/bin:/sbin:/usr/bin:/usr/sbin";
Anyone know why the util-linux path would fail here?
In the initrd, a lot of store paths do not exist, so my immediate guess is the store paths where util-linux is written at does not exist in the initrd. Take a look at boot.initrd.systemd.storePaths.
The problem is that the path option does not automatically get added to the store. Fixing that is something I’d like to do this week, since I’ve seen a number of people saying they wished it was.
Now, in this case, you actually just don’t need to set path, in which case PATH’s would be its default value which includes various things that are needed for boot, which includes all three of those packages (assuming your fileSystems includes btrfs). So your specific use case can just… not set path and be fine.
But yea, I think I’ll get something merged for adding path to the initrd automatically.
3 Likes
I guess I should clarify: This doesn’t mean path is just broken. It’s supposed to work if those paths are also in the initrd’s store, which can be done with boot.initrd.systemd.initrdBin = [ pkgs.foo ];. So combining that with path is meant to work. My point about path not working is just it probably should add those paths to the initrd’s store automatically.
There’s also another element at play here, which is that setting path shadows the default path rather than adding to it. Stage 2 is a little different in that the default path is minimal, rather than based on the whole system configuration, and that default path is added to rather than shadowed by user additions. But in stage 1 the default path includes all of initrdBin, and is replaced by the user path. In this particular circumstance, it combined with an odd implementation quirk of util-linux, which is that util-linux actually only contains a symlink to the mount binary, which is actually in its separate output util-linux.mount. So although the mount binary in util-linux.mount did exist in the initrd’s store, util-linux’s mount symlink did not exist, which is why adding util-linux broke the mount command. The user path setting shadowed the default path, which means util-linux was on PATH but util-linux.mount was not, but util-linux didn’t exist so its symlink to util-linux.mount was not found on PATH.
1 Like
Thank you for the detailed explanation @ElvishJerricco, that makes sense of the behaviour I observed and it’s clear how to get it working. I agree it would be an improvement to have stage 1 user path adding to the default, consistent with stage 2. Good to know about the symlinked binary quirk.