From 7508a0338e13e9068e70df45b31f104ef9fdfb6a Mon Sep 17 00:00:00 2001 From: r-vdp Date: Wed, 4 Mar 2026 23:01:43 +0100 Subject: [PATCH 1/3] nixos/qemu-vm: replace postBootCommands by a systemd service Reapply the systemd service approach for registering nix store paths in QEMU VMs that was introduced in #490967, fixing the ordering issue that caused it to be reverted in #496623. The service previously ran in multi-user.target with After=nix-daemon, racing with the test backdoor and other services. nix-store --load-db writes directly to the SQLite DB without needing the daemon, so move the service to sysinit.target with DefaultDependencies=false. This ensures the store DB is populated before any regular service starts. Requires the accompanying channel.nix change to also convert the channel init from boot.postBootCommands to a systemd service, since postBootCommands runs before systemd and can no longer rely on the store DB being loaded at that point. --- nixos/modules/virtualisation/qemu-vm.nix | 37 ++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index 5f001e05aa47..f0ff6fb1d475 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -1236,11 +1236,38 @@ in # allow `system.build.toplevel' to be included. (If we had a direct # reference to ${regInfo} here, then we would get a cyclic # dependency.) - boot.postBootCommands = lib.mkIf config.nix.enable '' - if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then - ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} - fi - ''; + systemd.services.register-nix-paths = lib.mkIf config.nix.enable { + # Run early during boot so the nix store DB is populated before any + # service (or test backdoor) tries to use nix commands. + # nix-store --load-db writes to the SQLite DB directly, so it does not + # need the nix-daemon. + unitConfig.DefaultDependencies = false; + wantedBy = [ + "sysinit.target" + ]; + before = [ + "sysinit.target" + "shutdown.target" + "nix-daemon.socket" + "nix-daemon.service" + ]; + after = [ + "local-fs.target" + ]; + conflicts = [ + "shutdown.target" + ]; + restartIfChanged = false; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then + ${lib.getExe' config.nix.package.out "nix-store"} --load-db < "''${BASH_REMATCH[1]}" + fi + ''; + }; boot.initrd.availableKernelModules = optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx" ++ optional (cfg.tpm.enable) "tpm_tis"; From d292d475265fa54d56a69408a23b6f27535f9af1 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Wed, 4 Mar 2026 23:01:52 +0100 Subject: [PATCH 2/3] nixos/channel: convert postBootCommands to a systemd service Replace boot.postBootCommands with a nix-channel-init systemd service. This is needed because the nix store DB registration in QEMU VMs now happens in a systemd service (register-nix-paths) rather than in postBootCommands, so the channel init must also be a systemd service to express the ordering dependency. The service runs in sysinit.target, after local-fs.target and (when present) register-nix-paths.service, but before nix-daemon.socket. Since nix-env is invoked as root before the daemon socket exists, it accesses the store directly. --- nixos/modules/installer/cd-dvd/channel.nix | 58 ++++++++++++++++------ 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/nixos/modules/installer/cd-dvd/channel.nix b/nixos/modules/installer/cd-dvd/channel.nix index e5fa5db420be..ef8606bfc977 100644 --- a/nixos/modules/installer/cd-dvd/channel.nix +++ b/nixos/modules/installer/cd-dvd/channel.nix @@ -54,19 +54,49 @@ in }; # Provide the NixOS/Nixpkgs sources in /etc/nixos. This is required - # for nixos-install. - boot.postBootCommands = lib.mkAfter '' - if ! [ -e /var/lib/nixos/did-channel-init ]; then - echo "unpacking the NixOS/Nixpkgs sources..." - mkdir -p /nix/var/nix/profiles/per-user/root - ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/per-user/root/channels \ - -i ${channelSources} --quiet --option build-use-substitutes false \ - ${lib.optionalString config.boot.initrd.systemd.enable "--option sandbox false"} # There's an issue with pivot_root - mkdir -m 0700 -p /root/.nix-defexpr - ln -s /nix/var/nix/profiles/per-user/root/channels /root/.nix-defexpr/channels - mkdir -m 0755 -p /var/lib/nixos - touch /var/lib/nixos/did-channel-init - fi - ''; + # for nixos-install. We use a systemd service rather than + # boot.postBootCommands so that ordering relative to other + # early-boot services (e.g. register-nix-paths in QEMU VMs) is + # explicit. + systemd.services.nix-channel-init = { + description = "Initialize NixOS Channel"; + # Run early so the channel is available before regular services. + # nix-env is invoked before nix-daemon.socket is up, so it + # accesses the store directly (we are root). + unitConfig.DefaultDependencies = false; + wantedBy = [ "sysinit.target" ]; + before = [ + "sysinit.target" + "shutdown.target" + "nix-daemon.socket" + "nix-daemon.service" + ]; + after = [ + "local-fs.target" + # In QEMU VMs the store DB is populated by register-nix-paths. + # On real hardware this unit does not exist and the dependency + # is silently ignored by systemd. + "register-nix-paths.service" + ]; + conflicts = [ "shutdown.target" ]; + restartIfChanged = false; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if ! [ -e /var/lib/nixos/did-channel-init ]; then + echo "unpacking the NixOS/Nixpkgs sources..." + mkdir -p /nix/var/nix/profiles/per-user/root + ${lib.getExe' config.nix.package.out "nix-env"} -p /nix/var/nix/profiles/per-user/root/channels \ + -i ${channelSources} --quiet --option build-use-substitutes false \ + ${lib.optionalString config.boot.initrd.systemd.enable "--option sandbox false"} # There's an issue with pivot_root + mkdir -m 0700 -p /root/.nix-defexpr + ln -s /nix/var/nix/profiles/per-user/root/channels /root/.nix-defexpr/channels + mkdir -m 0755 -p /var/lib/nixos + touch /var/lib/nixos/did-channel-init + fi + ''; + }; }; } From 6ab12a513a4debf1443a583fb5b2a3a072d561a7 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Wed, 4 Mar 2026 23:11:08 +0100 Subject: [PATCH 3/3] nixos: convert remaining postBootCommands that load the nix DB to systemd services Convert all remaining boot.postBootCommands that call nix-store --load-db into register-nix-paths systemd services for consistency with qemu-vm.nix and to make the ordering dependency with nix-channel-init explicit. All services use the same unit name (register-nix-paths) and the same sysinit.target ordering, so nix-channel-init's after=register-nix-paths.service dependency works uniformly across all platforms. Platform-specific logic (netboot password setup, sd-image partition expansion) is kept in the service script or in boot.postBootCommands as appropriate. --- nixos/modules/installer/cd-dvd/iso-image.nix | 35 ++++++-- nixos/modules/installer/netboot/netboot.nix | 41 ++++++--- nixos/modules/installer/sd-card/sd-image.nix | 85 +++++++++++++------ nixos/modules/profiles/docker-container.nix | 42 ++++++--- .../modules/virtualisation/lxc-container.nix | 35 ++++++-- nixos/modules/virtualisation/proxmox-lxc.nix | 41 ++++++--- 6 files changed, 202 insertions(+), 77 deletions(-) diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix index cf96b1abe880..0e519feca76a 100644 --- a/nixos/modules/installer/cd-dvd/iso-image.nix +++ b/nixos/modules/installer/cd-dvd/iso-image.nix @@ -1054,16 +1054,33 @@ in } ); - boot.postBootCommands = '' - # After booting, register the contents of the Nix store on the - # CD in the Nix database in the tmpfs. - ${config.nix.package.out}/bin/nix-store --load-db < /nix/store/nix-path-registration + systemd.services.register-nix-paths = { + description = "Register Nix Store Paths"; + unitConfig.DefaultDependencies = false; + wantedBy = [ "sysinit.target" ]; + before = [ + "sysinit.target" + "shutdown.target" + "nix-daemon.socket" + "nix-daemon.service" + ]; + after = [ "local-fs.target" ]; + conflicts = [ "shutdown.target" ]; + restartIfChanged = false; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + # After booting, register the contents of the Nix store on the + # CD in the Nix database in the tmpfs. + ${lib.getExe' config.nix.package.out "nix-store"} --load-db < /nix/store/nix-path-registration - # nixos-rebuild also requires a "system" profile and an - # /etc/NIXOS tag. - touch /etc/NIXOS - ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system - ''; + # nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag. + touch /etc/NIXOS + ${lib.getExe' config.nix.package.out "nix-env"} -p /nix/var/nix/profiles/system --set /run/current-system + ''; + }; # Add vfat support to the initrd to enable people to copy the # contents of the CD to a bootable USB stick. diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix index a4d9664a5531..4b5593d37d88 100644 --- a/nixos/modules/installer/netboot/netboot.nix +++ b/nixos/modules/installer/netboot/netboot.nix @@ -166,27 +166,46 @@ with lib; boot.loader.timeout = 10; + systemd.services.register-nix-paths = { + description = "Register Nix Store Paths"; + unitConfig.DefaultDependencies = false; + wantedBy = [ "sysinit.target" ]; + before = [ + "sysinit.target" + "shutdown.target" + "nix-daemon.socket" + "nix-daemon.service" + ]; + after = [ "local-fs.target" ]; + conflicts = [ "shutdown.target" ]; + restartIfChanged = false; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + # After booting, register the contents of the Nix store + # in the Nix database in the tmpfs. + ${lib.getExe' config.nix.package "nix-store"} --load-db < /nix/store/nix-path-registration + + # nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag. + touch /etc/NIXOS + ${lib.getExe' config.nix.package "nix-env"} -p /nix/var/nix/profiles/system --set /run/current-system + ''; + }; + boot.postBootCommands = '' - # After booting, register the contents of the Nix store - # in the Nix database in the tmpfs. - ${config.nix.package}/bin/nix-store --load-db < /nix/store/nix-path-registration - - # nixos-rebuild also requires a "system" profile and an - # /etc/NIXOS tag. - touch /etc/NIXOS - ${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system - # Set password for user nixos if specified on cmdline # Allows using nixos-anywhere in headless environments for o in $(