Merge pull request #17 from numtide/reboot

This commit is contained in:
Lassulus 2022-12-31 13:03:46 +01:00 committed by GitHub
commit b79af208e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 259 additions and 175 deletions

View File

@ -28,3 +28,51 @@ Afterwards you can just run:
The parameter passed to `--flake` should point to your nixos configuration
exposed in your flake (`nixosConfigurations.your-system` in the example above).
`nixos-remote --help`
``` shell
Usage: nixos-remote [options] ssh-host
Options:
* -f, --flake flake
set the flake to install the system from
* -s, --store-paths
set the store paths to the disko-script and nixos-system directly
if this is give, flake is not needed
* --no-ssh-copy
skip copying ssh-keys to target system
* --kexec url
use another kexec tarball to bootstrap NixOS
* --stop-after-disko
exit after disko formating, you can then proceed to install manually or some other way
* --no-reboot
do not reboot after installation
* --extra-files files
files to copy into the new nixos installation
* --debug
enable debug output
```
## Using your own kexec image
By default `nixos-remote` will download the kexec image from [here](https://github.com/nix-community/nixos-images#kexec-tarballs).
It is also possible to provide your own by providing a file to `--kexec`. The image will than uploaded prior to executing.
``` shell
nixos-remote \
--kexec "$(nix build --print-out-paths github:nix-community/nixos-images#packages.x86_64-linux.kexec-installer-nixos-unstable)/nixos-kexec-installer-x86_64-linux.tar.gz" \
--flake 'github:your-user/your-repo#your-system' \
root@yourip
```
`--kexec` can be useful for example for aarch64-linux, where there is no
pre-build image. The following example assumes that your local machine can
build for aarch64-linux either natively or through a remote builder
``` shell
nixos-remote \
--kexec "$(nix build --print-out-paths github:nix-community/nixos-images#packages.aarch64-linux.kexec-installer-nixos-unstable)/nixos-kexec-installer-aarch64-linux.tar.gz" \
--flake 'your-flake#your-system' \
root@yourip
```

View File

@ -29,15 +29,15 @@
checks.x86_64-linux =
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
inputs = {
inherit pkgs;
inherit (disko.nixosModules) disko;
kexec-installer = "${nixos-images.packages.${pkgs.system}.kexec-installer-nixos-unstable}/nixos-kexec-installer-${pkgs.stdenv.hostPlatform.system}.tar.gz";
};
in
{
from-nixos = import ./tests/from-nixos.nix {
inherit pkgs;
disko = disko.nixosModules.disko;
kexec-installer = "${nixos-images.packages.${pkgs.system}.kexec-installer-nixos-unstable}/nixos-kexec-installer-${pkgs.stdenv.hostPlatform.system}.tar.gz";
makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix");
eval-config = import (pkgs.path + "/nixos/lib/eval-config.nix");
};
from-nixos = import ./tests/from-nixos.nix inputs;
from-nixos-with-sudo = import ./tests/from-nixos-with-sudo.nix inputs;
};
};
}

View File

@ -18,6 +18,8 @@ Options:
use another kexec tarball to bootstrap NixOS
* --stop-after-disko
exit after disko formating, you can then proceed to install manually or some other way
* --no-reboot
do not reboot after installation
* --extra-files files
files to copy into the new nixos installation
* --debug
@ -31,7 +33,8 @@ abort() {
}
kexec_url=https://github.com/nix-community/nixos-images/releases/download/nixos-22.11/nixos-kexec-installer-x86_64-linux.tar.gz
debug=n
enable_debug=""
maybereboot="reboot"
while [[ $# -gt 0 ]]; do
case "$1" in
@ -40,8 +43,8 @@ while [[ $# -gt 0 ]]; do
shift
;;
-s | --store-paths)
disko_script=$2
nixos_system=$3
disko_script=$(readlink -f "$2")
nixos_system=$(readlink -f "$3")
shift
shift
;;
@ -57,7 +60,7 @@ while [[ $# -gt 0 ]]; do
no_ssh_copy=y
;;
--debug)
debug=y
enable_debug="-x"
set -x
;;
--extra-files)
@ -67,6 +70,9 @@ while [[ $# -gt 0 ]]; do
--stop-after-disko)
stop_after_disko=y
;;
--no-reboot)
maybereboot=""
;;
*)
if [[ -z ${ssh_connection:-} ]]; then
ssh_connection="$1"
@ -126,75 +132,73 @@ else
abort "flake must be set"
fi
# wait for machine to become reachable (possibly forever)
if [[ ${no_ssh_copy-n} != "y" ]]; then
until ssh-copy-id -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$ssh_connection"; do sleep 5; done
else
until ssh_ -o ConnectTimeout=10 -- exit 0; do sleep 5; done
fi
# first check if the remote system is kexec booted
if ssh_ -- test -e /etc/is_kexec; then
is_kexec=y
fi
if [[ ${is_kexec-n} != "y" ]]; then
# TODO we probably need an architecture detection here
ssh_ << SSH
set -efu
if [ "$debug" = "y" ]; then
set -x
fi
os=\$(uname)
if [[ "\$os" != "Linux" ]]; then
echo "This script requires Linux as the operating system, but got \${os}" >&2
exit 1
fi
if ! command -v tar >/dev/null 2>&1; then
echo "no tar command found, but required to unpack kexec tarball" >&2
exit 1
fi
rm -rf /root/kexec
mkdir -p /root/kexec
SSH
if [[ -f "$kexec_url" ]]; then
ssh_ 'tar -C /root/kexec -xvzf-' < "$kexec_url"
else
ssh_ << SSH
set -eu -o pipefail
if [ "$debug" = "y" ]; then
set -x
fi
fetch(){
if command -v curl >/dev/null 2>&1; then
curl --fail -Ss -L "\$1"
elif command -v wget >/dev/null 2>&1; then
wget "\$1" -O-
elif command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl
curl --fail -Ss -L "\$1"
else
echo "no downloader (curl or wget) found, bailing out" >&2
exit 1
fi
# TODO we probably need an architecture detection here
# TODO if we have specified a user here but we are already booted into the
# installer, than the user might not work anymore
until facts=$(ssh_ -o ConnectTimeout=10 -- <<SSH
set -efu ${enable_debug}
has(){
command -v tar >/dev/null && echo "y" || echo "n"
}
fetch "$kexec_url" | tar -C /root/kexec -xvzf-
cat <<FACTS
is_os=\$(uname)
is_kexec=\$(if test -f /etc/is_kexec; then echo "y"; else echo "n"; fi)
has_tar=\$(has tar)
has_sudo=\$(has sudo)
has_wget=\$(has wget)
has_curl=\$(has curl)
FACTS
SSH
); do
sleep 5
done
# make facts available in script
# shellcheck disable=SC2046
export $(echo "$facts" | xargs | grep -E '^(has|is)_[a-z0-9_]+=\S+')
if [[ ${has_tar-n} == "n" ]]; then
abort "no tar command found, but required to unpack kexec tarball"
fi
maybesudo=""
if [[ ${has_sudo-n} == "y" ]]; then
maybesudo="sudo"
fi
if [[ ${is_os-n} != "Linux" ]]; then
abort "This script requires Linux as the operating system, but got $is_os"
fi
ssh_ << SSH
export TMPDIR=/root/kexec
setsid /root/kexec/kexec/run
if [[ ${is_kexec-n} != "y" ]] && [[ ${no_ssh_copy-n} != "y" ]]; then
ssh-copy-id -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$ssh_connection"
fi
if [[ ${is_kexec-n} == "n" ]]; then
ssh_ << SSH
set -efu ${enable_debug}
"${maybesudo}" rm -rf /root/kexec
"${maybesudo}" mkdir -p /root/kexec
SSH
if [[ -f "$kexec_url" ]]; then
ssh_ "${maybesudo} tar -C /root/kexec -xvzf-" < "$kexec_url"
elif [[ ${has_curl-n} == "y" ]]; then
ssh_ "curl --fail -Ss -L '${kexec_url}' | ${maybesudo} tar -C /root/kexec -xvzf-"
elif [[ ${has_wget-n} == "y" ]]; then
ssh_ "wget '${kexec_url}' -O- | ${maybesudo} tar -C /root/kexec -xvzf-"
else
curl --fail -Ss -L "${kexec_url}" | ssh_ "${maybesudo} tar -C /root/kexec -xvzf-"
fi
ssh_ << SSH
TMPDIR=/root/kexec setsid ${maybesudo} /root/kexec/kexec/run
SSH
# wait for machine to become unreachable
while timeout_ssh_ -- exit 0; do sleep 1; done
# After kexec we explicitly set the user to root@
ssh_connection="root@${ssh_connection#*@}"
# watiting for machine to become available again
until ssh_ -o ConnectTimeout=10 -- exit 0; do sleep 5; done
fi
@ -213,11 +217,9 @@ if [[ -n ${extra_files:-} ]]; then
fi
rsync -vrlF -e "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" "$extra_files" "${ssh_connection}:/mnt/"
fi
ssh_ << SSH
set -efu
if [ "$debug" = "y" ]; then
set -x
fi
ssh_ <<SSH
set -efu ${enable_debug}
nixos-install --no-root-passwd --no-channel-copy --system "$nixos_system"
reboot
${maybereboot}
SSH

View File

@ -7,6 +7,7 @@
, coreutils
, shellcheck
, rsync
, curl
}:
let
runtimeDeps = [
@ -15,6 +16,7 @@ let
rsync
nix
coreutils
curl # when uploading tarballs
];
in
stdenvNoCC.mkDerivation {

View File

@ -0,0 +1,21 @@
(import ./lib/test-base.nix) {
name = "nixos-remote";
nodes = {
installer = ./modules/installer.nix;
installed = ./modules/installed.nix;
};
testScript = ''
start_all()
installer.succeed("""
eval $(ssh-agent)
ssh-add /etc/sshKey
${../nixos-remote} \
--no-ssh-copy-id \
--debug \
--kexec /etc/nixos-remote/kexec-installer \
--stop-after-disko \
--store-paths /etc/nixos-remote/disko /etc/nixos-remote/system-to-install \
nixos@installed >&2
""")
'';
}

View File

@ -1,97 +1,8 @@
{ pkgs ? import <nixpkgs> {}
, makeTest ? import <nixpkgs/nixos/tests/make-test-python.nix>
, eval-config ? import <nixpkgs/nixos/lib/eval-config.nix>
, disko ? "${builtins.fetchTarball "https://github.com/nix-community/disko/archive/master.tar.gz"}/module.nix"
, kexec-installer ? builtins.fetchurl "https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-kexec-installer-${pkgs.stdenv.hostPlatform.system}.tar.gz"
, ... }:
let
systemToInstall = { modulesPath, ... }: {
imports = [
disko
(modulesPath + "/testing/test-instrumentation.nix")
(modulesPath + "/profiles/qemu-guest.nix")
(modulesPath + "/profiles/minimal.nix")
];
networking.hostName = "nixos-remote";
documentation.enable = false;
hardware.enableAllFirmware = false;
networking.hostId = "8425e349"; # from profiles/base.nix, needed for zfs
boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms
boot.loader.grub.devices = [ "/dev/vda" ];
disko.devices = {
disk = {
vda = {
device = "/dev/vda";
type = "disk";
content = {
type = "table";
format = "gpt";
partitions = [
{
name = "boot";
type = "partition";
start = "0";
end = "1M";
part-type = "primary";
flags = ["bios_grub"];
}
{
type = "partition";
name = "ESP";
start = "1MiB";
end = "100MiB";
bootable = true;
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
}
{
name = "root";
type = "partition";
start = "100MiB";
end = "100%";
part-type = "primary";
bootable = true;
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
}
];
};
};
};
};
};
evaledSystem = eval-config {
modules = [ systemToInstall ];
system = "x86_64-linux";
};
in
makeTest {
(import ./lib/test-base.nix) {
name = "nixos-remote";
nodes = {
installer = {
documentation.enable = false;
environment.etc.sshKey = {
source = ./ssh-keys/ssh;
mode = "0600";
};
programs.ssh.startAgent = true;
system.extraDependencies = [
evaledSystem.config.system.build.disko
evaledSystem.config.system.build.toplevel
];
};
installed = {
virtualisation.memorySize = 4096;
documentation.enable = false;
services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keyFiles = [ ./ssh-keys/ssh.pub ];
};
installer = ./modules/installer.nix;
installed = ./modules/installed.nix;
};
testScript = ''
def create_test_machine(oldmachine=None, args={}): # taken from <nixpkgs/nixos/tests/installer.nix>
@ -103,9 +14,7 @@ makeTest {
} | args)
driver.machines.append(machine)
return machine
start_all()
installed.wait_for_unit("sshd.service")
installer.succeed("mkdir -p /tmp/extra-files/var/lib/secrets")
installer.succeed("echo value > /tmp/extra-files/var/lib/secrets/key")
installer.succeed("""
@ -114,9 +23,9 @@ makeTest {
${../nixos-remote} \
--no-ssh-copy-id \
--debug \
--kexec ${kexec-installer} \
--kexec /etc/nixos-remote/kexec-installer \
--extra-files /tmp/extra-files \
--store-paths ${toString evaledSystem.config.system.build.disko} ${toString evaledSystem.config.system.build.toplevel} \
--store-paths /etc/nixos-remote/disko /etc/nixos-remote/system-to-install \
root@installed >&2
""")
installed.shutdown()
@ -127,7 +36,4 @@ makeTest {
content = new_machine.succeed("cat /var/lib/secrets/key").strip()
assert "value" == content, f"secret does not have expected value: {content}"
'';
} {
pkgs = pkgs;
system = pkgs.system;
}

14
tests/lib/test-base.nix Normal file
View File

@ -0,0 +1,14 @@
test:
{ pkgs ? import <nixpkgs> {}, ... } @ args:
let
inherit (pkgs) lib;
nixos-lib = import (pkgs.path + "/nixos/lib") {};
in
(nixos-lib.runTest {
hostPkgs = pkgs;
# speed-up evaluation
defaults.documentation.enable = lib.mkDefault false;
# to accept external dependencies such as disko
node.specialArgs.inputs = args;
imports = [ test ];
}).config.result

View File

@ -0,0 +1,12 @@
{
virtualisation.memorySize = 4096;
services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keyFiles = [ ./ssh-keys/ssh.pub ];
users.users.nixos = {
isNormalUser = true;
openssh.authorizedKeys.keyFiles = [ ./ssh-keys/ssh.pub ];
extraGroups = [ "wheel" ];
};
security.sudo.enable = true;
security.sudo.wheelNeedsPassword = false;
}

View File

@ -0,0 +1,19 @@
{ config, lib, pkgs, inputs, ... }:
let
disko = inputs.disko; #or "${builtins.fetchTarball "https://github.com/nix-community/disko/archive/master.tar.gz"}/module.nix";
kexec-installer = inputs.kexec-installer; # or builtins.fetchurl "https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-kexec-installer-${pkgs.stdenv.hostPlatform.system}.tar.gz";
system-to-install = pkgs.nixos [
./system-to-install.nix
disko
];
in
{
programs.ssh.startAgent = true;
environment.etc = {
sshKey = { source = ./ssh-keys/ssh; mode = "0600"; };
"nixos-remote/disko".source = system-to-install.config.system.build.disko;
"nixos-remote/system-to-install".source = system-to-install.config.system.build.toplevel;
"nixos-remote/kexec-installer".source = kexec-installer;
};
}

View File

@ -0,0 +1,60 @@
{modulesPath, self, ...}: {
imports = [
(modulesPath + "/testing/test-instrumentation.nix")
(modulesPath + "/profiles/qemu-guest.nix")
(modulesPath + "/profiles/minimal.nix")
];
networking.hostName = "nixos-remote";
documentation.enable = false;
hardware.enableAllFirmware = false;
networking.hostId = "8425e349"; # from profiles/base.nix, needed for zfs
boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms
boot.loader.grub.devices = ["/dev/vda"];
disko.devices = {
disk = {
vda = {
device = "/dev/vda";
type = "disk";
content = {
type = "table";
format = "gpt";
partitions = [
{
name = "boot";
type = "partition";
start = "0";
end = "1M";
part-type = "primary";
flags = ["bios_grub"];
}
{
type = "partition";
name = "ESP";
start = "1MiB";
end = "100MiB";
bootable = true;
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
}
{
name = "root";
type = "partition";
start = "100MiB";
end = "100%";
part-type = "primary";
bootable = true;
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
}
];
};
};
};
};
}