From 366c8be2add24ecfb14ddc8452189358c9cb1439 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Mon, 21 Feb 2022 17:41:34 +0100 Subject: [PATCH 1/4] nixos/installer: add kexec-boot This module exposes a config.system.build.kexecBoot attribute, which returns a directory with kernel, initrd and a shell script running the necessary kexec commands. It's meant to be scp'ed to a machine with working ssh and kexec binary installed. This is useful for (cloud) providers where you can't boot a custom image, but get some Debian or Ubuntu installation. --- nixos/modules/installer/kexec/kexec-boot.nix | 50 ++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 nixos/modules/installer/kexec/kexec-boot.nix diff --git a/nixos/modules/installer/kexec/kexec-boot.nix b/nixos/modules/installer/kexec/kexec-boot.nix new file mode 100644 index 000000000000..c2f1a64a36a0 --- /dev/null +++ b/nixos/modules/installer/kexec/kexec-boot.nix @@ -0,0 +1,50 @@ +# This module exposes a config.system.build.kexecBoot attribute, +# which returns a directory with kernel, initrd and a shell script +# running the necessary kexec commands. + +# It's meant to be scp'ed to a machine with working ssh and kexec binary +# installed. + +# This is useful for (cloud) providers where you can't boot a custom image, but +# get some Debian or Ubuntu installation. + +{ pkgs +, modulesPath +, config +, ... +}: +{ + imports = [ + (modulesPath + "/installer/netboot/netboot-minimal.nix") + ]; + + config = { + system.build.kexecBoot = + let + kexecScript = pkgs.writeScript "kexec-boot" '' + #!/usr/bin/env bash + if ! kexec -v >/dev/null 2>&1; then + echo "kexec not found: please install kexec-tools" 2>&1 + exit 1 + fi + kexec --load ./bzImage \ + --initrd=./initrd.gz \ + --command-line "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}" + kexec -e + ''; in + pkgs.linkFarm "kexec-tree" [ + { + name = "initrd.gz"; + path = "${config.system.build.netbootRamdisk}/initrd"; + } + { + name = "bzImage"; + path = "${config.system.build.kernel}/bzImage"; + } + { + name = "kexec-boot"; + path = kexecScript; + } + ]; + }; +} From f0178e45eb6f218acef4e8ae46aaea052a2171c7 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Sat, 26 Feb 2022 22:38:28 +0100 Subject: [PATCH 2/4] nixosTests.kexec: extend with kexecBoot attribute Add a node2, which imports the kexec-boot.nix profile. Ensure node2 successfully boots up, then invoke the kexec-boot script from it on node1. --- nixos/tests/kexec.nix | 48 +++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/nixos/tests/kexec.nix b/nixos/tests/kexec.nix index 55b71e0999f6..7be04c740565 100644 --- a/nixos/tests/kexec.nix +++ b/nixos/tests/kexec.nix @@ -1,22 +1,44 @@ # Test whether fast reboots via kexec work. -import ./make-test-python.nix ({ pkgs, lib, ...} : { +import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "kexec"; meta = with lib.maintainers; { maintainers = [ eelco ]; }; - nodes.machine = { ... }: - { virtualisation.vlans = [ ]; }; + nodes = { + node1 = { ... }: { + virtualisation.vlans = [ ]; + virtualisation.memorySize = 2 * 1024; + }; - testScript = - '' - machine.wait_for_unit("multi-user.target") - machine.succeed('kexec --load /run/current-system/kernel --initrd /run/current-system/initrd --command-line "$(&2 &", check_return=False) - machine.connected = False - machine.connect() - machine.wait_for_unit("multi-user.target") - machine.shutdown() - ''; + node2 = { modulesPath, ... }: { + virtualisation.vlans = [ ]; + imports = [ + "${modulesPath}/installer/kexec/kexec-boot.nix" + ]; + }; + }; + + testScript = { nodes, ... }: '' + node1.wait_for_unit("multi-user.target") + node1.succeed('kexec --load /run/current-system/kernel --initrd /run/current-system/initrd --command-line "$(&2 &", check_return=False) + node1.connected = False + node1.connect() + node1.wait_for_unit("multi-user.target") + + + # Check the machine with kexec-boot.nix profile boots up + node2.wait_for_unit("multi-user.target") + node2.shutdown() + + # Kexec node1 to the toplevel of node2 via the kexec-boot script + node1.execute('${nodes.node2.config.system.build.kexecBoot}/kexec-boot', check_return=False) + node1.connected = False + node1.connect() + node1.wait_for_unit("multi-user.target") + + node1.shutdown() + ''; }) From 8ea2f75b72e11beb34121022e4d3243bd2fb6c89 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Sat, 26 Feb 2022 23:24:38 +0100 Subject: [PATCH 3/4] nixos/kexec-boot: use dirname of script to resolve bzImage and initrd.gz This will allow invoking the `kexec-boot` script without `cd`-ing into its folder first. --- nixos/modules/installer/kexec/kexec-boot.nix | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nixos/modules/installer/kexec/kexec-boot.nix b/nixos/modules/installer/kexec/kexec-boot.nix index c2f1a64a36a0..95ab774468c1 100644 --- a/nixos/modules/installer/kexec/kexec-boot.nix +++ b/nixos/modules/installer/kexec/kexec-boot.nix @@ -27,8 +27,9 @@ echo "kexec not found: please install kexec-tools" 2>&1 exit 1 fi - kexec --load ./bzImage \ - --initrd=./initrd.gz \ + SCRIPT_DIR=$( cd -- "$( dirname -- "''${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + kexec --load ''${SCRIPT_DIR}/bzImage \ + --initrd=''${SCRIPT_DIR}/initrd.gz \ --command-line "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}" kexec -e ''; in From 6ceedff331606021084e9d6b6d52216ab2c93025 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Sun, 27 Feb 2022 11:15:30 +0100 Subject: [PATCH 4/4] nixosTests.kexec: fix tests with kexecBoot format --- nixos/tests/kexec.nix | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nixos/tests/kexec.nix b/nixos/tests/kexec.nix index 7be04c740565..7e5cc010ef91 100644 --- a/nixos/tests/kexec.nix +++ b/nixos/tests/kexec.nix @@ -3,13 +3,17 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "kexec"; meta = with lib.maintainers; { - maintainers = [ eelco ]; + maintainers = [ flokli lassulus ]; }; nodes = { node1 = { ... }: { virtualisation.vlans = [ ]; - virtualisation.memorySize = 2 * 1024; + virtualisation.memorySize = 4 * 1024; + virtualisation.useBootLoader = true; + virtualisation.useEFIBoot = true; + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; }; node2 = { modulesPath, ... }: { @@ -28,16 +32,14 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { node1.connect() node1.wait_for_unit("multi-user.target") - # Check the machine with kexec-boot.nix profile boots up node2.wait_for_unit("multi-user.target") node2.shutdown() # Kexec node1 to the toplevel of node2 via the kexec-boot script + node1.succeed('touch /run/foo') node1.execute('${nodes.node2.config.system.build.kexecBoot}/kexec-boot', check_return=False) - node1.connected = False - node1.connect() - node1.wait_for_unit("multi-user.target") + node1.succeed('! test -e /run/foo') node1.shutdown() '';