diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index b8aeee8c11b3..df8db7f34d0d 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -55,11 +55,15 @@ let substituteInPlace $out/dry-activate --subst-var out chmod u+x $out/activate $out/dry-activate unset activationScript dryActivationScript - ${pkgs.stdenv.shellDryRun} $out/activate - ${pkgs.stdenv.shellDryRun} $out/dry-activate - cp ${config.system.build.bootStage2} $out/init - substituteInPlace $out/init --subst-var-by systemConfig $out + ${if config.boot.initrd.systemd.enable then '' + cp ${config.system.build.bootStage2} $out/prepare-root + substituteInPlace $out/prepare-root --subst-var-by systemConfig $out + ln -s "$systemd/lib/systemd/systemd" $out/init + '' else '' + cp ${config.system.build.bootStage2} $out/init + substituteInPlace $out/init --subst-var-by systemConfig $out + ''} ln -s ${config.system.build.etc}/etc $out/etc ln -s ${config.system.path} $out/sw diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh index 096a051f8684..f2a839d07868 100755 --- a/nixos/modules/system/boot/stage-2-init.sh +++ b/nixos/modules/system/boot/stage-2-init.sh @@ -5,28 +5,30 @@ systemConfig=@systemConfig@ export HOME=/root PATH="@path@" -# Process the kernel command line. -for o in $(>>\e[0m" -echo + # Print a greeting. + echo + echo -e "\e[1;32m<<< NixOS Stage 2 >>>\e[0m" + echo -# Normally, stage 1 mounts the root filesystem read/writable. -# However, in some environments, stage 2 is executed directly, and the -# root is read-only. So make it writable here. -if [ -z "$container" ]; then - mount -n -o remount,rw none / + # Normally, stage 1 mounts the root filesystem read/writable. + # However, in some environments, stage 2 is executed directly, and the + # root is read-only. So make it writable here. + if [ -z "$container" ]; then + mount -n -o remount,rw none / + fi fi @@ -39,6 +41,12 @@ if [ ! -e /proc/1 ]; then local options="$3" local fsType="$4" + # We must not overwrite this mount because it's bind-mounted + # from stage 1's /run + if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ] && [ "${mountPoint}" = /run ]; then + return + fi + install -m 0755 -d "$mountPoint" mount -n -t "$fsType" -o "$options" "$device" "$mountPoint" } @@ -46,7 +54,11 @@ if [ ! -e /proc/1 ]; then fi -echo "booting system configuration $systemConfig" > /dev/kmsg +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ]; then + echo "booting system configuration ${systemConfig}" +else + echo "booting system configuration $systemConfig" > /dev/kmsg +fi # Make /nix/store a read-only bind mount to enforce immutability of @@ -68,24 +80,26 @@ if [ -n "@readOnlyStore@" ]; then fi -# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable. -if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then - resolvconf -m 1000 -a host &1 {logErrFd}>&2 -if test -w /dev/kmsg; then - exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do - if test -n "$line"; then - echo "<7>stage-2-init: $line" > /dev/kmsg - fi - done) 2>&1 -else - mkdir -p /run/log - exec > >(tee -i /run/log/stage-2-init.log) 2>&1 + # Log the script output to /dev/kmsg or /run/log/stage-2-init.log. + # Only at this point are all the necessary prerequisites ready for these commands. + exec {logOutFd}>&1 {logErrFd}>&2 + if test -w /dev/kmsg; then + exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do + if test -n "$line"; then + echo "<7>stage-2-init: $line" > /dev/kmsg + fi + done) 2>&1 + else + mkdir -p /run/log + exec > >(tee -i /run/log/stage-2-init.log) 2>&1 + fi fi @@ -116,11 +130,15 @@ ln -sfn "$systemConfig" /run/booted-system : >> /etc/machine-id -# Reset the logging file descriptors. -exec 1>&$logOutFd 2>&$logErrFd -exec {logOutFd}>&- {logErrFd}>&- +# No need to restore the stdout/stderr streams we never redirected and +# especially no need to start systemd +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then + # Reset the logging file descriptors. + exec 1>&$logOutFd 2>&$logErrFd + exec {logOutFd}>&- {logErrFd}>&- -# Start systemd in a clean environment. -echo "starting systemd..." -exec @systemdExecutable@ "$@" + # Start systemd in a clean environment. + echo "starting systemd..." + exec @systemdExecutable@ "$@" +fi diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index c383486bb0bc..dab06a7b98e7 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -125,7 +125,7 @@ let }; initrdBinEnv = pkgs.buildEnv { - name = "initrd-emergency-env"; + name = "initrd-bin-env"; paths = map getBin cfg.initrdBin; pathsToLink = ["/bin" "/sbin"]; postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -s '${v}' $out/bin/'${n}'") cfg.extraBin); @@ -355,8 +355,9 @@ in { boot.initrd.availableKernelModules = [ "autofs4" ]; # systemd needs this for some features boot.initrd.systemd = { - initrdBin = [pkgs.bash pkgs.coreutils pkgs.kmod cfg.package] ++ config.system.fsPackages; + initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages; extraBin = { + less = "${pkgs.less}/bin/less"; mount = "${cfg.package.util-linux}/bin/mount"; umount = "${cfg.package.util-linux}/bin/umount"; }; @@ -367,7 +368,7 @@ in { "/etc/systemd/system.conf".text = '' [Manager] - DefaultEnvironment=PATH=/bin:/sbin + DefaultEnvironment=PATH=/bin:/sbin ${optionalString (isBool cfg.emergencyAccess && cfg.emergencyAccess) "SYSTEMD_SULOGIN_FORCE=1"} ''; "/etc/fstab".source = fstab; @@ -394,7 +395,9 @@ in { "${cfg.package}/lib/systemd/systemd-journald" "${cfg.package}/lib/systemd/systemd-makefs" "${cfg.package}/lib/systemd/systemd-modules-load" + "${cfg.package}/lib/systemd/systemd-random-seed" "${cfg.package}/lib/systemd/systemd-remount-fs" + "${cfg.package}/lib/systemd/systemd-shutdown" "${cfg.package}/lib/systemd/systemd-sulogin-shell" "${cfg.package}/lib/systemd/systemd-sysctl" "${cfg.package}/lib/systemd/systemd-udevd" @@ -410,7 +413,7 @@ in { "${cfg.package.util-linux}/bin/sulogin" # so NSS can look up usernames - "${pkgs.glibc}/lib/libnss_files.so" + "${pkgs.glibc}/lib/libnss_files.so.2" ] ++ jobScripts; targets.initrd.aliases = ["default.target"]; @@ -428,9 +431,6 @@ in { (v: let n = escapeSystemdPath v.where; in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts); - services.emergency = mkIf (isBool cfg.emergencyAccess && cfg.emergencyAccess) { - environment.SYSTEMD_SULOGIN_FORCE = "1"; - }; # The unit in /run/systemd/generator shadows the unit in # /etc/systemd/system, but will still apply drop-ins from # /etc/systemd/system/foo.service.d/ @@ -445,6 +445,67 @@ in { '')]; services."systemd-makefs@".unitConfig.IgnoreOnIsolate = true; services."systemd-growfs@".unitConfig.IgnoreOnIsolate = true; + + services.initrd-nixos-activation = { + after = [ "initrd-fs.target" ]; + requiredBy = [ "initrd.target" ]; + unitConfig.AssertPathExists = "/etc/initrd-release"; + serviceConfig.Type = "oneshot"; + description = "NixOS Activation"; + + script = /* bash */ '' + set -uo pipefail + export PATH="/bin:${cfg.package.util-linux}/bin" + + # Figure out what closure to boot + closure= + for o in $(< /proc/cmdline); do + case $o in + init=*) + IFS== read -r -a initParam <<< "$o" + closure="$(dirname "''${initParam[1]}")" + ;; + esac + done + + # Sanity check + if [ -z "''${closure:-}" ]; then + echo 'No init= parameter on the kernel command line' >&2 + exit 1 + fi + + # If we are not booting a NixOS closure (e.g. init=/bin/sh), + # we don't know what root to prepare so we don't do anything + if ! [ -x "/sysroot$closure/prepare-root" ]; then + echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf + echo "$closure does not look like a NixOS installation - not activating" + exit 0 + fi + echo 'NEW_INIT=' > /etc/switch-root.conf + + + # We need to propagate /run for things like /run/booted-system + # and /run/current-system. + mkdir -p /sysroot/run + mount --bind /run /sysroot/run + + # Initialize the system + export IN_NIXOS_SYSTEMD_STAGE1=true + exec chroot /sysroot $closure/prepare-root + ''; + }; + + # This will either call systemctl with the new init as the last parameter (which + # is the case when not booting a NixOS system) or with an empty string, causing + # systemd to bypass its verification code that checks whether the next file is a systemd + # and using its compiled-in value + services.initrd-switch-root.serviceConfig = { + EnvironmentFile = "-/etc/switch-root.conf"; + ExecStart = [ + "" + ''systemctl --no-block switch-root /sysroot "''${NEW_INIT}"'' + ]; + }; }; }; } diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix index ba62cdf3bbc7..959cc87c0f26 100644 --- a/nixos/tests/systemd-initrd-simple.nix +++ b/nixos/tests/systemd-initrd-simple.nix @@ -14,14 +14,31 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: { testScript = '' import subprocess - oldAvail = machine.succeed("df --output=avail / | sed 1d") - machine.shutdown() + with subtest("handover to stage-2 systemd works"): + machine.wait_for_unit("multi-user.target") + machine.succeed("systemd-analyze | grep -q '(initrd)'") # direct handover + machine.succeed("touch /testfile") # / is writable + machine.fail("touch /nix/store/testfile") # /nix/store is not writable + # Special filesystems are mounted by systemd + machine.succeed("[ -e /run/booted-system ]") # /run + machine.succeed("[ -e /sys/class ]") # /sys + machine.succeed("[ -e /dev/null ]") # /dev + machine.succeed("[ -e /proc/1 ]") # /proc + # stage-2-init mounted more special filesystems + machine.succeed("[ -e /dev/shm ]") # /dev/shm + machine.succeed("[ -e /dev/pts/ptmx ]") # /dev/pts + machine.succeed("[ -e /run/keys ]") # /run/keys - subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"]) - machine.start() - newAvail = machine.succeed("df --output=avail / | sed 1d") + with subtest("growfs works"): + oldAvail = machine.succeed("df --output=avail / | sed 1d") + machine.shutdown() - assert int(oldAvail) < int(newAvail), "File system did not grow" + subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"]) + + machine.start() + newAvail = machine.succeed("df --output=avail / | sed 1d") + + assert int(oldAvail) < int(newAvail), "File system did not grow" ''; }) diff --git a/pkgs/os-specific/linux/kbd/default.nix b/pkgs/os-specific/linux/kbd/default.nix index 23a523b15d72..4c89d8c82197 100644 --- a/pkgs/os-specific/linux/kbd/default.nix +++ b/pkgs/os-specific/linux/kbd/default.nix @@ -68,6 +68,7 @@ stdenv.mkDerivation rec { passthru.tests = { inherit (nixosTests) keymap kbd-setfont-decompress kbd-update-search-paths-patch; }; + passthru.gzip = gzip; meta = with lib; { homepage = "https://kbd-project.org/"; diff --git a/pkgs/os-specific/linux/systemd/default.nix b/pkgs/os-specific/linux/systemd/default.nix index 73c27b0b61f0..e130abbf27ff 100644 --- a/pkgs/os-specific/linux/systemd/default.nix +++ b/pkgs/os-specific/linux/systemd/default.nix @@ -675,7 +675,7 @@ stdenv.mkDerivation { # runtime; otherwise we can't and we need to reboot. interfaceVersion = 2; - inherit withCryptsetup util-linux; + inherit withCryptsetup util-linux kmod kbd; tests = { inherit (nixosTests) switchTest;