summary refs log tree commit diff
diff options
context:
space:
mode:
authorJanne Heß <janne@hess.ooo>2022-04-04 12:57:32 +0100
committerJanne Heß <janne@hess.ooo>2022-04-11 20:04:33 +0100
commit3df2691e6b5500e853bb62591fce613603fc2478 (patch)
treec11616c68683962608af2fff5126891f3a6532ce
parentadab6ce5527a8c87f87ed0cae60a020cf9a8d942 (diff)
downloadnixpkgs-3df2691e6b5500e853bb62591fce613603fc2478.tar
nixpkgs-3df2691e6b5500e853bb62591fce613603fc2478.tar.gz
nixpkgs-3df2691e6b5500e853bb62591fce613603fc2478.tar.bz2
nixpkgs-3df2691e6b5500e853bb62591fce613603fc2478.tar.lz
nixpkgs-3df2691e6b5500e853bb62591fce613603fc2478.tar.xz
nixpkgs-3df2691e6b5500e853bb62591fce613603fc2478.tar.zst
nixpkgs-3df2691e6b5500e853bb62591fce613603fc2478.zip
nixos/stage-1-systemd: Handover between the systemds directly
-rw-r--r--nixos/modules/system/activation/top-level.nix12
-rwxr-xr-xnixos/modules/system/boot/stage-2-init.sh108
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix61
-rw-r--r--nixos/tests/systemd-initrd-simple.nix29
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 $(</proc/cmdline); do
-    case $o in
-        boot.debugtrace)
-            # Show each command.
-            set -x
-            ;;
-    esac
-done
-
-
-# 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 /
+if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
+    # Process the kernel command line.
+    for o in $(</proc/cmdline); do
+        case $o in
+            boot.debugtrace)
+                # Show each command.
+                set -x
+                ;;
+        esac
+    done
+
+
+    # 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 /
+    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 </etc/resolv.conf
-fi
+if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
+    # Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
+    if [ -n "@useHostResolvConf@" ] && [ -e /etc/resolv.conf ]; then
+        resolvconf -m 1000 -a host </etc/resolv.conf
+    fi
 
 
-# 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
+    # 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"
   '';
 })