diff --git a/nixos/modules/installer/kexec/kexec-boot.nix b/nixos/modules/installer/kexec/kexec-boot.nix new file mode 100644 index 000000000000..95ab774468c1 --- /dev/null +++ b/nixos/modules/installer/kexec/kexec-boot.nix @@ -0,0 +1,51 @@ +# 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 + 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 + 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; + } + ]; + }; +} diff --git a/nixos/tests/kexec.nix b/nixos/tests/kexec.nix index 55b71e0999f6..7e5cc010ef91 100644 --- a/nixos/tests/kexec.nix +++ b/nixos/tests/kexec.nix @@ -1,22 +1,46 @@ # 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 ]; + maintainers = [ flokli lassulus ]; }; - nodes.machine = { ... }: - { virtualisation.vlans = [ ]; }; + nodes = { + node1 = { ... }: { + virtualisation.vlans = [ ]; + virtualisation.memorySize = 4 * 1024; + virtualisation.useBootLoader = true; + virtualisation.useEFIBoot = true; + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + }; - 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.succeed('touch /run/foo') + node1.execute('${nodes.node2.config.system.build.kexecBoot}/kexec-boot', check_return=False) + node1.succeed('! test -e /run/foo') + + node1.shutdown() + ''; })