From 3df2691e6b5500e853bb62591fce613603fc2478 Mon Sep 17 00:00:00 2001 From: Janne Heß Date: Mon, 4 Apr 2022 12:57:32 +0100 Subject: nixos/stage-1-systemd: Handover between the systemds directly --- nixos/modules/system/activation/top-level.nix | 12 ++- nixos/modules/system/boot/stage-2-init.sh | 108 +++++++++++++++----------- nixos/modules/system/boot/systemd/initrd.nix | 61 +++++++++++++++ nixos/tests/systemd-initrd-simple.nix | 29 +++++-- 4 files changed, 155 insertions(+), 55 deletions(-) diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index b8aeee8c11b..df8db7f34d0 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 096a051f868..f2a839d0786 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 - - -# 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 / +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then + # Process the kernel command line. + for o in $(>>\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 / + 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 681293d8fed..dab06a7b98e 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -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 ba62cdf3bbc..959cc87c0f2 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" ''; }) -- cgit 1.4.1