From f61ff68f6377b9d311edadae90c53eec990f98f2 Mon Sep 17 00:00:00 2001 From: Ilan Joselevich Date: Tue, 16 Jun 2026 17:15:31 +0300 Subject: [PATCH] nixos-init: skip the etc overlay for a non-NixOS init= find-etc verified the init= was inside a NixOS toplevel and bailed otherwise, failing initrd-find-etc.service. The etc-overlay mounts require it, so for a non-NixOS init= (e.g. init=/bin/sh) the initrd dropped to emergency mode before initrd-init could switch-root into the target. Make find-etc skip silently for a non-NixOS init=, leaving the /etc-basedir and /etc-metadata-image symlinks uncreated, and gate the etc-metadata mount, the /sysroot/etc overlay and the rw-etc service on those symlinks with ConditionPathExists so they skip instead of fail. initrd-init then switch-roots into the non-NixOS init directly. The NixOS path is unchanged: the symlinks exist, so the conditions hold. Extend the systemd-initrd-non-nixos test with a second node enabling system.nixos-init.enable, so both the bash initrd-nixos-activation path and the nixos-init path are covered. Assisted-by: Claude:claude-opus-4-8 --- nixos/modules/system/etc/etc-activation.nix | 8 ++++++ nixos/tests/systemd-initrd-non-nixos.nix | 31 +++++++++++++++------ pkgs/by-name/ni/nixos-init/src/find_etc.rs | 17 +++++++++-- pkgs/by-name/ni/nixos-init/src/lib.rs | 10 ------- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/nixos/modules/system/etc/etc-activation.nix b/nixos/modules/system/etc/etc-activation.nix index b00c25e37802..31842fc9c0c6 100644 --- a/nixos/modules/system/etc/etc-activation.nix +++ b/nixos/modules/system/etc/etc-activation.nix @@ -71,6 +71,10 @@ RequiresMountsFor = [ "/sysroot/nix/store" ]; + # find-etc only creates this symlink for a NixOS init. For a + # non-NixOS init= (e.g. init=/bin/sh) it is absent, so skip the + # mount instead of failing the whole initrd. + ConditionPathExists = "/etc-metadata-image"; }; requires = [ config.boot.initrd.systemd.services.initrd-find-etc.name @@ -123,6 +127,8 @@ "/run/nixos-etc-metadata" ]; DefaultDependencies = false; + # Skip for a non-NixOS init=, see the metadata mount above. + ConditionPathExists = "/etc-basedir"; }; } ]; @@ -140,6 +146,8 @@ # before the overlay is mounted. "/run/nixos-etc-metadata" ]; + # Skip for a non-NixOS init=, see the metadata mount above. + ConditionPathExists = "/etc-metadata-image"; }; serviceConfig = { Type = "oneshot"; diff --git a/nixos/tests/systemd-initrd-non-nixos.nix b/nixos/tests/systemd-initrd-non-nixos.nix index 225a98afbca2..0f564af6504a 100644 --- a/nixos/tests/systemd-initrd-non-nixos.nix +++ b/nixos/tests/systemd-initrd-non-nixos.nix @@ -10,11 +10,8 @@ let echo "${marker}" > /dev/console exec ${pkgs.coreutils}/bin/sleep infinity ''; -in -{ - name = "systemd-initrd-non-nixos"; - nodes.machine = { + common = { boot.initrd.systemd.enable = true; virtualisation = { @@ -35,6 +32,20 @@ in argument = "ID=test-non-nixos"; }; }; +in +{ + name = "systemd-initrd-non-nixos"; + + nodes = { + bashActivation = common; + + nixosInit = { + imports = [ common ]; + system.nixos-init.enable = true; + system.etc.overlay.enable = true; + services.userborn.enable = true; + }; + }; testScript = '' import os @@ -43,11 +54,15 @@ in # the default one, so this boots our non-NixOS init. os.environ["QEMU_KERNEL_PARAMS"] = "init=${lib.getExe nonNixosInit}" - machine.start() + start_all() - # If activation does not skip the non-NixOS init, switch-root is blocked and - # the machine drops to emergency mode: the marker never appears and this + # If a code path does not skip the non-NixOS init, switch-root is blocked and + # the machine drops to emergency mode: the marker never appears and the wait # times out. - machine.wait_for_console_text("${marker}", timeout=300) + with subtest("bash initrd-nixos-activation skips a non-NixOS init"): + bashActivation.wait_for_console_text("${marker}", timeout=300) + + with subtest("nixos-init switches to a non-NixOS init directly"): + nixosInit.wait_for_console_text("${marker}", timeout=300) ''; } diff --git a/pkgs/by-name/ni/nixos-init/src/find_etc.rs b/pkgs/by-name/ni/nixos-init/src/find_etc.rs index 8b994e1d7534..d06f7bbb0114 100644 --- a/pkgs/by-name/ni/nixos-init/src/find_etc.rs +++ b/pkgs/by-name/ni/nixos-init/src/find_etc.rs @@ -3,7 +3,7 @@ use std::{os::unix, path::Path}; use anyhow::{Context, Result}; use crate::config::Config; -use crate::{SYSROOT_PATH, find_toplevel_in_prefix, resolve_in_prefix}; +use crate::{SYSROOT_PATH, find_init_in_prefix, resolve_in_prefix, verify_init_is_nixos}; /// Entrypoint for the `find-etc` binary. /// @@ -12,7 +12,20 @@ use crate::{SYSROOT_PATH, find_toplevel_in_prefix, resolve_in_prefix}; /// This avoids needing a reference to the toplevel embedded in the initrd and thus reduces the /// need to re-build it. pub fn find_etc() -> Result<()> { - let toplevel = find_toplevel_in_prefix(SYSROOT_PATH)?; + let init_in_sysroot = + find_init_in_prefix(SYSROOT_PATH).context("Failed to find init in sysroot")?; + + // A non-NixOS init= (e.g. init=/bin/sh) has no etc metadata image. Skip + // without creating the symlinks: the etc-overlay mounts are gated on them + // and so skip too, and initrd-init switches root to the init directly. + let Ok(toplevel) = verify_init_is_nixos(SYSROOT_PATH, &init_in_sysroot) else { + log::info!( + "{} is not a NixOS system - not setting up the etc overlay.", + init_in_sysroot.display() + ); + return Ok(()); + }; + let config = Config::from_toplevel(&toplevel, SYSROOT_PATH)?; let basedir = config diff --git a/pkgs/by-name/ni/nixos-init/src/lib.rs b/pkgs/by-name/ni/nixos-init/src/lib.rs index 3d38065f9a88..bbb4abdb829b 100644 --- a/pkgs/by-name/ni/nixos-init/src/lib.rs +++ b/pkgs/by-name/ni/nixos-init/src/lib.rs @@ -27,16 +27,6 @@ pub use crate::{ pub const SYSROOT_PATH: &str = "/sysroot"; -/// Find the path to the toplevel closure of the system in a prefix. -/// -/// Uses the `init=` parameter on the kernel command-line. -/// -/// Returns the relative path of the init to the prefix, e.g. without the `/sysroot` prefix. -pub fn find_toplevel_in_prefix(prefix: &str) -> Result { - let init_in_sysroot = find_init_in_prefix(prefix)?; - verify_init_is_nixos(prefix, init_in_sysroot) -} - /// Verify that an init path is inside a `NixOS` toplevel directory. /// /// If the path is verified, returns the path to the toplevel.