Skip to content

Commit abb0d72

Browse files
authored
Merge pull request #537 from nix-community/ubuntu-kexec-test
Add Ubuntu kexec test
2 parents a64838b + 853dede commit abb0d72

File tree

7 files changed

+159
-30
lines changed

7 files changed

+159
-30
lines changed

flake.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
nixos-images.url = "github:nix-community/nixos-images";
1212
nixos-images.inputs.nixos-unstable.follows = "nixpkgs";
1313
nixos-images.inputs.nixos-stable.follows = "nixos-stable";
14+
# https://github.com/numtide/nix-vm-test/pull/105
15+
nix-vm-test = { url = "github:Mic92/nix-vm-test"; inputs.nixpkgs.follows = "nixpkgs"; };
1416

1517
# used for development
1618
treefmt-nix = { url = "github:numtide/treefmt-nix"; inputs.nixpkgs.follows = "nixpkgs"; };

src/nixos-anywhere.sh

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ hasCurl=
5858
hasSetsid=
5959
hasNixOSFacter=
6060

61-
sshKeyDir=$(mktemp -d)
62-
trap 'rm -rf "$sshKeyDir"' EXIT
63-
mkdir -p "$sshKeyDir"
61+
tempDir=$(mktemp -d)
62+
trap 'rm -rf "$tempDir"' EXIT
63+
mkdir -p "$tempDir"
6464

6565
declare -A diskEncryptionKeys=()
6666
declare -A extraFilesOwnership=()
6767
declare -a nixCopyOptions=()
68-
declare -a sshArgs=("-o" "IdentitiesOnly=yes" "-i" "$sshKeyDir/nixos-anywhere" "-o" "UserKnownHostsFile=/dev/null" "-o" "StrictHostKeyChecking=no")
68+
declare -a sshArgs=("-o" "IdentitiesOnly=yes" "-i" "$tempDir/nixos-anywhere" "-o" "UserKnownHostsFile=/dev/null" "-o" "StrictHostKeyChecking=no")
6969

7070
showUsage() {
7171
cat <<USAGE
@@ -465,19 +465,26 @@ runVmTest() {
465465

466466
uploadSshKey() {
467467
# ssh-copy-id requires this directory
468-
mkdir -p "$HOME/.ssh/"
468+
local sshCopyHome="$HOME"
469+
if ! mkdir -p "$HOME/.ssh/" 2>/dev/null; then
470+
# Fallback: create a temporary home directory for ssh-copy-id in tempDir
471+
sshCopyHome="$tempDir/ssh-home"
472+
mkdir -p "$sshCopyHome/.ssh"
473+
echo "Warning: Could not create $HOME/.ssh, using temporary directory: $sshCopyHome"
474+
fi
475+
469476
if [[ -n ${sshPrivateKeyFile} ]]; then
470-
cp "$sshPrivateKeyFile" "$sshKeyDir/nixos-anywhere"
471-
ssh-keygen -y -f "$sshKeyDir/nixos-anywhere" >"$sshKeyDir/nixos-anywhere.pub"
477+
cp "$sshPrivateKeyFile" "$tempDir/nixos-anywhere"
478+
ssh-keygen -y -f "$tempDir/nixos-anywhere" >"$tempDir/nixos-anywhere.pub"
472479
else
473480
# we generate a temporary ssh keypair that we can use during nixos-anywhere
474-
ssh-keygen -t ed25519 -f "$sshKeyDir"/nixos-anywhere -P "" -C "nixos-anywhere" >/dev/null
481+
ssh-keygen -t ed25519 -f "$tempDir"/nixos-anywhere -P "" -C "nixos-anywhere" >/dev/null
475482
fi
476483

477484
step Uploading install SSH keys
478485
until
479486
if [[ ${envPassword} == y ]]; then
480-
sshpass -e \
487+
HOME="$sshCopyHome" sshpass -e \
481488
ssh-copy-id \
482489
-o ConnectTimeout=10 \
483490
"${sshArgs[@]}" \
@@ -486,7 +493,7 @@ uploadSshKey() {
486493
# To override `IdentitiesOnly=yes` set in `sshArgs` we need to set
487494
# `IdentitiesOnly=no` first as the first time an SSH option is
488495
# specified on the command line takes precedence
489-
ssh-copy-id \
496+
HOME="$sshCopyHome" ssh-copy-id \
490497
-o IdentitiesOnly=no \
491498
-o ConnectTimeout=10 \
492499
"${sshArgs[@]}" \
@@ -702,7 +709,7 @@ runDisko() {
702709
# If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store`
703710
diskoScript=$(
704711
nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}" \
705-
--eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$sshKeyDir%2Fnixos-anywhere&$sshStoreSettings"
712+
--eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere&$sshStoreSettings"
706713
)
707714
fi
708715
@@ -724,7 +731,7 @@ nixosInstall() {
724731
# If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store`
725732
nixosSystem=$(
726733
nixBuild "${flake}#${flakeAttr}.system.build.toplevel" \
727-
--eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$sshKeyDir%2Fnixos-anywhere&remote-store=local%3Froot=%2Fmnt&$sshStoreSettings"
734+
--eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere&remote-store=local%3Froot=%2Fmnt&$sshStoreSettings"
728735
)
729736
fi
730737
@@ -823,8 +830,8 @@ main() {
823830
fi
824831
825832
if [[ -n ${SSH_PRIVATE_KEY} ]] && [[ -z ${sshPrivateKeyFile} ]]; then
826-
# $sshKeyDir is getting deleted on trap EXIT
827-
sshPrivateKeyFile="$sshKeyDir/from-env"
833+
# $tempDir is getting deleted on trap EXIT
834+
sshPrivateKeyFile="$tempDir/from-env"
828835
(
829836
umask 077
830837
printf '%s\n' "$SSH_PRIVATE_KEY" >"$sshPrivateKeyFile"

tests/flake-module.nix

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@
33
{
44
flake.checks.x86_64-linux = withSystem "x86_64-linux" ({ pkgs, system, inputs', config, ... }:
55
let
6+
system-to-install = pkgs.nixos [
7+
./modules/system-to-install.nix
8+
inputs.disko.nixosModules.disko
9+
];
610
testInputsUnstable = {
711
inherit pkgs;
812
inherit (inputs.disko.nixosModules) disko;
913
nixos-anywhere = config.packages.nixos-anywhere;
1014
kexec-installer = "${inputs'.nixos-images.packages.kexec-installer-nixos-unstable-noninteractive}/nixos-kexec-installer-noninteractive-${system}.tar.gz";
15+
inherit system-to-install;
1116
};
1217
testInputsStable = testInputsUnstable // {
1318
kexec-installer = "${inputs'.nixos-images.packages.kexec-installer-nixos-stable-noninteractive}/nixos-kexec-installer-noninteractive-${system}.tar.gz";
1419
};
20+
ubuntuTestInputs = testInputsUnstable // {
21+
nix-vm-test = inputs.nix-vm-test;
22+
};
1523
in
1624
{
1725
from-nixos = import ./from-nixos.nix testInputsUnstable;
@@ -21,5 +29,6 @@
2129
from-nixos-with-generated-config = import ./from-nixos-generate-config.nix testInputsUnstable;
2230
from-nixos-build-on-remote = import ./from-nixos-build-on-remote.nix testInputsUnstable;
2331
from-nixos-separated-phases = import ./from-nixos-separated-phases.nix testInputsUnstable;
32+
ubuntu-kexec-test = import ./ubuntu-kexec-test.nix ubuntuTestInputs;
2433
});
2534
}

tests/lib/test-base.nix

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
test:
2-
{ pkgs ? import <nixpkgs> { }
3-
, nixos-anywhere ? pkgs.callPackage ../../src { }
4-
, disko ? "${builtins.fetchTarball "https://github.com/nix-community/disko/archive/master.tar.gz"}/module.nix"
5-
, kexec-installer ? builtins.fetchurl "https://github.com/nix-community/nixos-images/releases/download/nixos-unstable/nixos-kexec-installer-noninteractive-${pkgs.stdenv.hostPlatform.system}.tar.gz"
2+
{ pkgs
3+
, nixos-anywhere
4+
, disko
5+
, kexec-installer
6+
, system-to-install
67
, ...
78
}:
89
let
@@ -14,6 +15,6 @@ in
1415
# speed-up evaluation
1516
defaults.documentation.enable = lib.mkDefault false;
1617
# to accept external dependencies such as disko
17-
node.specialArgs.inputs = { inherit nixos-anywhere disko kexec-installer; };
18+
node.specialArgs.inputs = { inherit nixos-anywhere disko kexec-installer system-to-install; };
1819
imports = [ test ];
1920
}).config.result

tests/modules/installer.nix

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
{ pkgs, inputs, ... }:
2-
let
3-
disko = inputs.disko;
4-
kexec-installer = inputs.kexec-installer;
5-
system-to-install = pkgs.nixos [
6-
./system-to-install.nix
7-
disko
8-
];
9-
in
102
{
113
system.activationScripts.rsa-key = ''
124
${pkgs.coreutils}/bin/install -D -m600 ${./ssh-keys/ssh} /root/.ssh/install_key
@@ -15,8 +7,8 @@ in
157
environment.systemPackages = [ inputs.nixos-anywhere ];
168

179
environment.etc = {
18-
"nixos-anywhere/disko".source = system-to-install.config.system.build.diskoScriptNoDeps;
19-
"nixos-anywhere/system-to-install".source = system-to-install.config.system.build.toplevel;
20-
"nixos-anywhere/kexec-installer".source = kexec-installer;
10+
"nixos-anywhere/disko".source = inputs.system-to-install.config.system.build.diskoScriptNoDeps;
11+
"nixos-anywhere/system-to-install".source = inputs.system-to-install.config.system.build.toplevel;
12+
"nixos-anywhere/kexec-installer".source = inputs.kexec-installer;
2113
};
2214
}

tests/ubuntu-kexec-test.nix

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{ pkgs, nixos-anywhere, kexec-installer, nix-vm-test, system-to-install, ... }:
2+
3+
(nix-vm-test.lib.${pkgs.system}.ubuntu."24_04" {
4+
sharedDirs = { };
5+
6+
# Configure VM with 2GB memory
7+
machineConfigModule = { ... }: {
8+
nodes.vm.virtualisation.memorySize = 2048;
9+
};
10+
11+
# The test script
12+
testScript = ''
13+
# Python imports
14+
import subprocess
15+
import tempfile
16+
import shutil
17+
import os
18+
19+
# Wait for the system to be fully booted
20+
vm.wait_for_unit("multi-user.target")
21+
22+
# Unmask SSH service (which is masked by default in the test VM)
23+
vm.succeed("systemctl unmask ssh.service ssh.socket")
24+
25+
# Generate SSH host keys (required for SSH to start)
26+
vm.succeed("ssh-keygen -A")
27+
28+
# Setup SSH with the existing keys
29+
vm.succeed("mkdir -p /root/.ssh")
30+
vm.succeed(
31+
"echo '${builtins.replaceStrings ["\n"] [""] (builtins.readFile ./modules/ssh-keys/ssh.pub)}' > /root/.ssh/authorized_keys"
32+
)
33+
vm.succeed("chmod 644 /root/.ssh/authorized_keys")
34+
35+
# Setup SSH for connection from host
36+
vm.succeed(
37+
"sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config"
38+
)
39+
40+
# Start SSH service
41+
vm.succeed("systemctl start ssh")
42+
43+
# Wait for SSH to be available
44+
vm.wait_for_open_port(22)
45+
46+
# Forward SSH port using vm.forward_port method
47+
ssh_port = 2222
48+
vm.forward_port(host_port=ssh_port, guest_port=22)
49+
50+
# Use temporary file for SSH key with automatic cleanup
51+
with tempfile.NamedTemporaryFile(mode='w', delete=True, suffix='_ssh_key') as temp_key:
52+
temp_key_path = temp_key.name
53+
54+
# Copy SSH private key to temp file with correct permissions
55+
shutil.copy2("${./modules/ssh-keys/ssh}", temp_key_path)
56+
os.chmod(temp_key_path, 0o600)
57+
58+
nixos_anywhere_cmd = [
59+
"${nixos-anywhere}/bin/nixos-anywhere",
60+
"-i", temp_key_path,
61+
"--ssh-port", str(ssh_port),
62+
"--post-kexec-ssh-port", "2222",
63+
"--phases", "kexec",
64+
"--kexec", "${kexec-installer}",
65+
"--store-paths", "${system-to-install.config.system.build.diskoScriptNoDeps}",
66+
"${system-to-install.config.system.build.toplevel}",
67+
"--debug",
68+
"root@localhost"
69+
]
70+
71+
result = subprocess.run(nixos_anywhere_cmd, check=False)
72+
73+
if result.returncode != 0:
74+
print(f"nixos-anywhere failed with exit code {result.returncode}")
75+
vm.succeed("dmesg | tail -n 50")
76+
vm.succeed("journalctl -n 50")
77+
raise Exception(f"nixos-anywhere command failed with exit code {result.returncode}")
78+
79+
# Test SSH connection to verify we're in NixOS kexec environment
80+
check_cmd = [
81+
"${pkgs.openssh}/bin/ssh", "-v",
82+
"-i", temp_key_path,
83+
"-p", "2222",
84+
"-o", "StrictHostKeyChecking=no",
85+
"-o", "UserKnownHostsFile=/dev/null",
86+
"root@localhost",
87+
"cat /etc/os-release"
88+
]
89+
90+
test_result = subprocess.run(check_cmd, check=True, stdout=subprocess.PIPE, text=True)
91+
assert "nixos" in test_result.stdout.lower(), f"Expected NixOS environment but got: {test_result.stdout}"
92+
93+
# After kexec we no longer have the machine driver,
94+
# so we need to let the VM crash because the test driver backdoor gets confused by the terminal output.
95+
vm.crash()
96+
'';
97+
}).sandboxed

0 commit comments

Comments
 (0)