Merge pull request #177010 from flokli/make-kexec

nixos/modules/installer/kexec/kexec-boot.nix: move into nixos/lib, expose `kexec` nixos/release.nix
This commit is contained in:
Florian Klink 2022-06-12 10:07:35 +02:00 committed by GitHub
commit 2b9be3117e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 201 additions and 55 deletions

View file

@ -0,0 +1,94 @@
<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-booting-via-kexec">
<title><quote>Booting</quote> into NixOS via kexec</title>
<para>
In some cases, your system might already be booted into/preinstalled
with another Linux distribution, and booting NixOS by attaching an
installation image is quite a manual process.
</para>
<para>
This is particularly useful for (cloud) providers where you cant
boot a custom image, but get some Debian or Ubuntu installation.
</para>
<para>
In these cases, it might be easier to use <literal>kexec</literal>
to <quote>jump into NixOS</quote> from the running system, which
only assumes <literal>bash</literal> and <literal>kexec</literal> to
be installed on the machine.
</para>
<para>
Note that kexec may not work correctly on some hardware, as devices
are not fully re-initialized in the process. In practice, this
however is rarely the case.
</para>
<para>
To build the necessary files from your current version of nixpkgs,
you can run:
</para>
<programlisting>
nix-build -A kexec.x86_64-linux '&lt;nixpkgs/nixos/release.nix&gt;'
</programlisting>
<para>
This will create a <literal>result</literal> directory containing
the following:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>bzImage</literal> (the Linux kernel)
</para>
</listitem>
<listitem>
<para>
<literal>initrd</literal> (the initrd file)
</para>
</listitem>
<listitem>
<para>
<literal>kexec-boot</literal> (a shellscript invoking
<literal>kexec</literal>)
</para>
</listitem>
</itemizedlist>
<para>
These three files are meant to be copied over to the other already
running Linux Distribution.
</para>
<para>
Note its symlinks pointing elsewhere, so <literal>cd</literal> in,
and use <literal>scp * root@$destination</literal> to copy it over,
rather than rsync.
</para>
<para>
Once you finished copying, execute <literal>kexec-boot</literal>
<emphasis>on the destination</emphasis>, and after some seconds, the
machine should be booting into an (ephemeral) NixOS installation
medium.
</para>
<para>
In case you want to describe your own system closure to kexec into,
instead of the default installer image, you can build your own
<literal>configuration.nix</literal>:
</para>
<programlisting language="bash">
{ modulesPath, ... }: {
imports = [
(modulesPath + &quot;/installer/netboot/netboot-minimal.nix&quot;)
];
services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keys = [
&quot;my-ssh-pubkey&quot;
];
}
</programlisting>
<programlisting>
nix-build '&lt;nixpkgs/nixos&gt;' \
--arg configuration ./configuration.nix
--attr config.system.build.kexecTree
</programlisting>
<para>
Make sure your <literal>configuration.nix</literal> does still
import <literal>netboot-minimal.nix</literal> (or
<literal>netboot-base.nix</literal>).
</para>
</section>

View file

@ -638,6 +638,7 @@ $ passwd eelco
<title>Additional installation notes</title> <title>Additional installation notes</title>
<xi:include href="installing-usb.section.xml" /> <xi:include href="installing-usb.section.xml" />
<xi:include href="installing-pxe.section.xml" /> <xi:include href="installing-pxe.section.xml" />
<xi:include href="installing-kexec.section.xml" />
<xi:include href="installing-virtualbox-guest.section.xml" /> <xi:include href="installing-virtualbox-guest.section.xml" />
<xi:include href="installing-from-other-distro.section.xml" /> <xi:include href="installing-from-other-distro.section.xml" />
<xi:include href="installing-behind-a-proxy.section.xml" /> <xi:include href="installing-behind-a-proxy.section.xml" />

View file

@ -0,0 +1,64 @@
# "Booting" into NixOS via kexec {#sec-booting-via-kexec}
In some cases, your system might already be booted into/preinstalled with
another Linux distribution, and booting NixOS by attaching an installation
image is quite a manual process.
This is particularly useful for (cloud) providers where you can't boot a custom
image, but get some Debian or Ubuntu installation.
In these cases, it might be easier to use `kexec` to "jump into NixOS" from the
running system, which only assumes `bash` and `kexec` to be installed on the
machine.
Note that kexec may not work correctly on some hardware, as devices are not
fully re-initialized in the process. In practice, this however is rarely the
case.
To build the necessary files from your current version of nixpkgs,
you can run:
```ShellSession
nix-build -A kexec.x86_64-linux '<nixpkgs/nixos/release.nix>'
```
This will create a `result` directory containing the following:
- `bzImage` (the Linux kernel)
- `initrd` (the initrd file)
- `kexec-boot` (a shellscript invoking `kexec`)
These three files are meant to be copied over to the other already running
Linux Distribution.
Note it's symlinks pointing elsewhere, so `cd` in, and use
`scp * root@$destination` to copy it over, rather than rsync.
Once you finished copying, execute `kexec-boot` *on the destination*, and after
some seconds, the machine should be booting into an (ephemeral) NixOS
installation medium.
In case you want to describe your own system closure to kexec into, instead of
the default installer image, you can build your own `configuration.nix`:
```nix
{ modulesPath, ... }: {
imports = [
(modulesPath + "/installer/netboot/netboot-minimal.nix")
];
services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keys = [
"my-ssh-pubkey"
];
}
```
```ShellSession
nix-build '<nixpkgs/nixos>' \
--arg configuration ./configuration.nix
--attr config.system.build.kexecTree
```
Make sure your `configuration.nix` does still import `netboot-minimal.nix` (or
`netboot-base.nix`).

View file

@ -476,6 +476,7 @@ With a partitioned disk.
```{=docbook} ```{=docbook}
<xi:include href="installing-usb.section.xml" /> <xi:include href="installing-usb.section.xml" />
<xi:include href="installing-pxe.section.xml" /> <xi:include href="installing-pxe.section.xml" />
<xi:include href="installing-kexec.section.xml" />
<xi:include href="installing-virtualbox-guest.section.xml" /> <xi:include href="installing-virtualbox-guest.section.xml" />
<xi:include href="installing-from-other-distro.section.xml" /> <xi:include href="installing-from-other-distro.section.xml" />
<xi:include href="installing-behind-a-proxy.section.xml" /> <xi:include href="installing-behind-a-proxy.section.xml" />

View file

@ -1,51 +0,0 @@
# 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}/${config.system.boot.loader.kernelFile}";
}
{
name = "kexec-boot";
path = kexecScript;
}
];
};
}

View file

@ -101,6 +101,37 @@ with lib;
boot boot
''; '';
# A script invoking kexec on ./bzImage and ./initrd.gz.
# Usually used through system.build.kexecTree, but exposed here for composability.
system.build.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
'';
# A tree containing initrd.gz, bzImage and a kexec-boot script.
system.build.kexecTree = pkgs.linkFarm "kexec-tree" [
{
name = "initrd.gz";
path = "${config.system.build.netbootRamdisk}/initrd";
}
{
name = "bzImage";
path = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}";
}
{
name = "kexec-boot";
path = config.system.build.kexecScript;
}
];
boot.loader.timeout = 10; boot.loader.timeout = 10;
boot.postBootCommands = boot.postBootCommands =

View file

@ -151,6 +151,13 @@ in rec {
# Build the initial ramdisk so Hydra can keep track of its size over time. # Build the initial ramdisk so Hydra can keep track of its size over time.
initialRamdisk = buildFromConfig ({ ... }: { }) (config: config.system.build.initialRamdisk); initialRamdisk = buildFromConfig ({ ... }: { }) (config: config.system.build.initialRamdisk);
kexec = forMatchingSystems supportedSystems (system: (import lib/eval-config.nix {
inherit system;
modules = [
./modules/installer/netboot/netboot-minimal.nix
];
}).config.system.build.kexecTree);
netboot = forMatchingSystems supportedSystems (system: makeNetboot { netboot = forMatchingSystems supportedSystems (system: makeNetboot {
module = ./modules/installer/netboot/netboot-minimal.nix; module = ./modules/installer/netboot/netboot-minimal.nix;
inherit system; inherit system;

View file

@ -18,8 +18,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
virtualisation.vlans = [ ]; virtualisation.vlans = [ ];
environment.systemPackages = [ pkgs.hello ]; environment.systemPackages = [ pkgs.hello ];
imports = [ imports = [
"${modulesPath}/installer/kexec/kexec-boot.nix" "${modulesPath}/installer/netboot/netboot-minimal.nix"
"${modulesPath}/profiles/minimal.nix"
]; ];
}; };
}; };
@ -33,14 +32,14 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
node1.connect() node1.connect()
node1.wait_for_unit("multi-user.target") node1.wait_for_unit("multi-user.target")
# Check if the machine with kexec-boot.nix profile boots up # Check if the machine with netboot-minimal.nix profile boots up
node2.wait_for_unit("multi-user.target") node2.wait_for_unit("multi-user.target")
node2.shutdown() node2.shutdown()
# Kexec node1 to the toplevel of node2 via the kexec-boot script # Kexec node1 to the toplevel of node2 via the kexec-boot script
node1.succeed('touch /run/foo') node1.succeed('touch /run/foo')
node1.fail('hello') node1.fail('hello')
node1.execute('${nodes.node2.config.system.build.kexecBoot}/kexec-boot', check_return=False) node1.execute('${nodes.node2.config.system.build.kexecTree}/kexec-boot', check_return=False)
node1.succeed('! test -e /run/foo') node1.succeed('! test -e /run/foo')
node1.succeed('hello') node1.succeed('hello')
node1.succeed('[ "$(hostname)" = "node2" ]') node1.succeed('[ "$(hostname)" = "node2" ]')