Merge pull request 'add flash command and tests' (#916) from Mic92-main into main
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
./impure/flake-module.nix
|
./impure/flake-module.nix
|
||||||
./backups/flake-module.nix
|
./backups/flake-module.nix
|
||||||
./installation/flake-module.nix
|
./installation/flake-module.nix
|
||||||
|
./flash/flake-module.nix
|
||||||
];
|
];
|
||||||
perSystem = { pkgs, lib, self', ... }: {
|
perSystem = { pkgs, lib, self', ... }: {
|
||||||
checks =
|
checks =
|
||||||
|
|||||||
46
checks/flash/flake-module.nix
Normal file
46
checks/flash/flake-module.nix
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{ self, ... }:
|
||||||
|
{
|
||||||
|
perSystem = { nodes, pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
dependencies = [
|
||||||
|
self
|
||||||
|
pkgs.stdenv.drvPath
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.toplevel
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.clan.deployment.file
|
||||||
|
self.inputs.nixpkgs.legacyPackages.${pkgs.hostPlatform.system}.disko
|
||||||
|
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
|
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||||
|
flash =
|
||||||
|
(import ../lib/test-base.nix)
|
||||||
|
{
|
||||||
|
name = "flash";
|
||||||
|
nodes.target = {
|
||||||
|
virtualisation.emptyDiskImages = [ 4096 ];
|
||||||
|
virtualisation.memorySize = 3000;
|
||||||
|
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||||
|
environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||||
|
|
||||||
|
nix.settings = {
|
||||||
|
substituters = lib.mkForce [ ];
|
||||||
|
hashed-mirrors = null;
|
||||||
|
connect-timeout = lib.mkForce 3;
|
||||||
|
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||||
|
experimental-features = [
|
||||||
|
"nix-command"
|
||||||
|
"flakes"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
machine.succeed("clan --flake ${../..} flash --debug --yes --disk main /dev/vdb test_install_machine")
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
{ inherit pkgs self; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,12 +21,6 @@ in
|
|||||||
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
|
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
|
||||||
(modulesPath + "/profiles/qemu-guest.nix")
|
(modulesPath + "/profiles/qemu-guest.nix")
|
||||||
];
|
];
|
||||||
fileSystems."/nix/store" = lib.mkForce {
|
|
||||||
device = "nix-store";
|
|
||||||
fsType = "9p";
|
|
||||||
neededForBoot = true;
|
|
||||||
options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ];
|
|
||||||
};
|
|
||||||
clan.diskLayouts.singleDiskExt4.device = "/dev/vdb";
|
clan.diskLayouts.singleDiskExt4.device = "/dev/vdb";
|
||||||
|
|
||||||
environment.etc."install-successful".text = "ok";
|
environment.etc."install-successful".text = "ok";
|
||||||
@@ -92,22 +86,22 @@ in
|
|||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
def create_test_machine(oldmachine=None, args={}): # taken from <nixpkgs/nixos/tests/installer.nix>
|
def create_test_machine(oldmachine=None, args={}): # taken from <nixpkgs/nixos/tests/installer.nix>
|
||||||
|
startCommand = "${pkgs.qemu_test}/bin/qemu-kvm"
|
||||||
|
startCommand += " -cpu max -m 1024 -virtfs local,path=/nix/store,security_model=none,mount_tag=nix-store"
|
||||||
|
startCommand += f' -drive file={oldmachine.state_dir}/empty0.qcow2,id=drive1,if=none,index=1,werror=report'
|
||||||
|
startCommand += ' -device virtio-blk-pci,drive=drive1'
|
||||||
machine = create_machine({
|
machine = create_machine({
|
||||||
"qemuFlags":
|
"startCommand": startCommand,
|
||||||
'-cpu max -m 1024 -virtfs local,path=/nix/store,security_model=none,mount_tag=nix-store,'
|
|
||||||
f' -drive file={oldmachine.state_dir}/empty0.qcow2,id=drive1,if=none,index=1,werror=report'
|
|
||||||
f' -device virtio-blk-pci,drive=drive1',
|
|
||||||
} | args)
|
} | args)
|
||||||
driver.machines.append(machine)
|
driver.machines.append(machine)
|
||||||
return machine
|
return machine
|
||||||
|
|
||||||
|
|
||||||
start_all()
|
start_all()
|
||||||
|
|
||||||
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
||||||
client.wait_until_succeeds("ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
client.wait_until_succeeds("ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
||||||
|
|
||||||
client.succeed("clan --debug --flake ${../..} machines install test_install_machine root@target >&2")
|
client.succeed("clan --debug --flake ${../..} machines install --yes test_install_machine root@target >&2")
|
||||||
try:
|
try:
|
||||||
target.shutdown()
|
target.shutdown()
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ class Driver:
|
|||||||
|
|
||||||
self.machines = []
|
self.machines = []
|
||||||
for container in containers:
|
for container in containers:
|
||||||
name_match = re.match(r".*-nixos-system-(.+)-\d.+", container.name)
|
name_match = re.match(r".*-nixos-system-(.+)-(.+)", container.name)
|
||||||
if not name_match:
|
if not name_match:
|
||||||
raise ValueError(f"Unable to extract hostname from {container.name}")
|
raise ValueError(f"Unable to extract hostname from {container.name}")
|
||||||
name = name_match.group(1)
|
name = name_match.group(1)
|
||||||
|
|||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -78,11 +78,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1708847675,
|
"lastModified": 1709764733,
|
||||||
"narHash": "sha256-RUZ7KEs/a4EzRELYDGnRB6i7M1Izii3JD/LyzH0c6Tg=",
|
"narHash": "sha256-GptBnEUy8IcRrnd8X5WBJPDXG7M4bjj8OG4Wjg8dCDs=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2a34566b67bef34c551f204063faeecc444ae9da",
|
"rev": "edf9f14255a7ac20f8da7b70609e980a964fca7a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -48,8 +48,13 @@
|
|||||||
cat /var/shared/qrcode.utf8
|
cat /var/shared/qrcode.utf8
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
boot.loader.grub.efiInstallAsRemovable = true;
|
|
||||||
boot.loader.grub.efiSupport = true;
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
|
||||||
|
# Grub doesn't find devices for both BIOS and UEFI?
|
||||||
|
|
||||||
|
#boot.loader.grub.efiInstallAsRemovable = true;
|
||||||
|
#boot.loader.grub.efiSupport = true;
|
||||||
disko.devices = {
|
disko.devices = {
|
||||||
disk = {
|
disk = {
|
||||||
stick = {
|
stick = {
|
||||||
@@ -59,10 +64,10 @@
|
|||||||
content = {
|
content = {
|
||||||
type = "gpt";
|
type = "gpt";
|
||||||
partitions = {
|
partitions = {
|
||||||
boot = {
|
#boot = {
|
||||||
size = "1M";
|
# size = "1M";
|
||||||
type = "EF02"; # for grub MBR
|
# type = "EF02"; # for grub MBR
|
||||||
};
|
#};
|
||||||
ESP = {
|
ESP = {
|
||||||
size = "100M";
|
size = "100M";
|
||||||
type = "EF00";
|
type = "EF00";
|
||||||
|
|||||||
@@ -1,51 +1,110 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
from collections.abc import Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .cmd import Log, run
|
||||||
|
from .errors import ClanError
|
||||||
from .machines.machines import Machine
|
from .machines.machines import Machine
|
||||||
from .secrets.generate import generate_secrets
|
from .nix import nix_shell
|
||||||
|
from .secrets.modules import SecretStoreBase
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def flash_machine(machine: Machine, device: str | None = None) -> None:
|
def flash_machine(
|
||||||
|
machine: Machine, disks: dict[str, str], dry_run: bool, debug: bool
|
||||||
|
) -> None:
|
||||||
secrets_module = importlib.import_module(machine.secrets_module)
|
secrets_module = importlib.import_module(machine.secrets_module)
|
||||||
secret_store = secrets_module.SecretStore(machine=machine)
|
secret_store: SecretStoreBase = secrets_module.SecretStore(machine=machine)
|
||||||
|
|
||||||
generate_secrets(machine)
|
|
||||||
|
|
||||||
with TemporaryDirectory() as tmpdir_:
|
with TemporaryDirectory() as tmpdir_:
|
||||||
tmpdir = Path(tmpdir_)
|
tmpdir = Path(tmpdir_)
|
||||||
upload_dir_ = machine.secrets_upload_directory
|
upload_dir = machine.secrets_upload_directory
|
||||||
|
|
||||||
if upload_dir_.startswith("/"):
|
if upload_dir.startswith("/"):
|
||||||
upload_dir_ = upload_dir_[1:]
|
local_dir = tmpdir / upload_dir[1:]
|
||||||
upload_dir = tmpdir / upload_dir_
|
else:
|
||||||
upload_dir.mkdir(parents=True)
|
local_dir = tmpdir / upload_dir
|
||||||
secret_store.upload(upload_dir)
|
|
||||||
|
|
||||||
fs_image = machine.build_nix("config.system.clan.iso")
|
local_dir.mkdir(parents=True)
|
||||||
print(fs_image)
|
secret_store.upload(local_dir)
|
||||||
|
disko_install = []
|
||||||
|
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
if shutil.which("sudo") is None:
|
||||||
|
raise ClanError(
|
||||||
|
"sudo is required to run disko-install as a non-root user"
|
||||||
|
)
|
||||||
|
disko_install.append("sudo")
|
||||||
|
|
||||||
|
disko_install.append("disko-install")
|
||||||
|
if dry_run:
|
||||||
|
disko_install.append("--dry-run")
|
||||||
|
if debug:
|
||||||
|
disko_install.append("--debug")
|
||||||
|
for name, device in disks.items():
|
||||||
|
disko_install.extend(["--disk", name, device])
|
||||||
|
|
||||||
|
disko_install.extend(["--extra-files", str(local_dir), upload_dir])
|
||||||
|
disko_install.extend(["--flake", str(machine.flake) + "#" + machine.name])
|
||||||
|
|
||||||
|
cmd = nix_shell(
|
||||||
|
["nixpkgs#disko"],
|
||||||
|
disko_install,
|
||||||
|
)
|
||||||
|
print("$", " ".join(map(shlex.quote, cmd)))
|
||||||
|
run(cmd, log=Log.BOTH, error_msg=f"Failed to flash {machine}")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FlashOptions:
|
class FlashOptions:
|
||||||
flake: Path
|
flake: Path
|
||||||
machine: str
|
machine: str
|
||||||
device: str | None
|
disks: dict[str, str]
|
||||||
|
dry_run: bool
|
||||||
|
confirm: bool
|
||||||
|
debug: bool
|
||||||
|
|
||||||
|
|
||||||
|
class AppendDiskAction(argparse.Action):
|
||||||
|
def __init__(self, option_strings: str, dest: str, **kwargs: Any) -> None:
|
||||||
|
super().__init__(option_strings, dest, **kwargs)
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
parser: argparse.ArgumentParser,
|
||||||
|
namespace: argparse.Namespace,
|
||||||
|
values: str | Sequence[str] | None,
|
||||||
|
option_string: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
disks = getattr(namespace, self.dest)
|
||||||
|
assert isinstance(values, list), "values must be a list"
|
||||||
|
disks[values[0]] = values[1]
|
||||||
|
|
||||||
|
|
||||||
def flash_command(args: argparse.Namespace) -> None:
|
def flash_command(args: argparse.Namespace) -> None:
|
||||||
opts = FlashOptions(
|
opts = FlashOptions(
|
||||||
flake=args.flake,
|
flake=args.flake,
|
||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
device=args.device,
|
disks=args.disk,
|
||||||
|
dry_run=args.dry_run,
|
||||||
|
confirm=not args.yes,
|
||||||
|
debug=args.debug,
|
||||||
)
|
)
|
||||||
machine = Machine(opts.machine, flake=opts.flake)
|
machine = Machine(opts.machine, flake=opts.flake)
|
||||||
flash_machine(machine, device=opts.device)
|
if opts.confirm and not opts.dry_run:
|
||||||
|
disk_str = ", ".join(f"{name}={device}" for name, device in opts.disks.items())
|
||||||
|
ask = input(f"Install {machine.name} to {disk_str}? [y/N] ")
|
||||||
|
if ask != "y":
|
||||||
|
return
|
||||||
|
flash_machine(machine, disks=opts.disks, dry_run=opts.dry_run, debug=opts.debug)
|
||||||
|
|
||||||
|
|
||||||
def register_parser(parser: argparse.ArgumentParser) -> None:
|
def register_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
@@ -55,8 +114,30 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
help="machine to install",
|
help="machine to install",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--device",
|
"--disk",
|
||||||
type=str,
|
type=str,
|
||||||
help="device to flash the system to",
|
nargs=2,
|
||||||
|
metavar=("name", "value"),
|
||||||
|
action=AppendDiskAction,
|
||||||
|
help="device to flash to",
|
||||||
|
default={},
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--yes",
|
||||||
|
action="store_true",
|
||||||
|
help="do not ask for confirmation",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
help="Only build the system, don't flash it",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
help="Print debug information",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
)
|
)
|
||||||
parser.set_defaults(func=flash_command)
|
parser.set_defaults(func=flash_command)
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class InstallOptions:
|
|||||||
machine: str
|
machine: str
|
||||||
target_host: str
|
target_host: str
|
||||||
kexec: str | None
|
kexec: str | None
|
||||||
|
confirm: bool
|
||||||
|
|
||||||
|
|
||||||
def install_command(args: argparse.Namespace) -> None:
|
def install_command(args: argparse.Namespace) -> None:
|
||||||
@@ -71,10 +72,16 @@ def install_command(args: argparse.Namespace) -> None:
|
|||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
target_host=args.target_host,
|
target_host=args.target_host,
|
||||||
kexec=args.kexec,
|
kexec=args.kexec,
|
||||||
|
confirm=not args.yes,
|
||||||
)
|
)
|
||||||
machine = Machine(opts.machine, flake=opts.flake)
|
machine = Machine(opts.machine, flake=opts.flake)
|
||||||
machine.target_host_address = opts.target_host
|
machine.target_host_address = opts.target_host
|
||||||
|
|
||||||
|
if opts.confirm:
|
||||||
|
ask = input(f"Install {machine.name} to {opts.target_host}? [y/N] ")
|
||||||
|
if ask != "y":
|
||||||
|
return
|
||||||
|
|
||||||
install_nixos(machine, kexec=opts.kexec)
|
install_nixos(machine, kexec=opts.kexec)
|
||||||
|
|
||||||
|
|
||||||
@@ -84,6 +91,12 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
type=str,
|
type=str,
|
||||||
help="use another kexec tarball to bootstrap NixOS",
|
help="use another kexec tarball to bootstrap NixOS",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--yes",
|
||||||
|
action="store_true",
|
||||||
|
help="do not ask for confirmation",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
type=str,
|
type=str,
|
||||||
|
|||||||
@@ -174,12 +174,13 @@ def run_vm(
|
|||||||
if vm.graphics and not vm.waypipe:
|
if vm.graphics and not vm.waypipe:
|
||||||
packages.append("nixpkgs#virt-viewer")
|
packages.append("nixpkgs#virt-viewer")
|
||||||
remote_viewer_mimetypes = module_root() / "vms" / "mimetypes"
|
remote_viewer_mimetypes = module_root() / "vms" / "mimetypes"
|
||||||
env[
|
env["XDG_DATA_DIRS"] = (
|
||||||
"XDG_DATA_DIRS"
|
f"{remote_viewer_mimetypes}:{env.get('XDG_DATA_DIRS', '')}"
|
||||||
] = f"{remote_viewer_mimetypes}:{env.get('XDG_DATA_DIRS', '')}"
|
)
|
||||||
|
|
||||||
with start_waypipe(qemu_cmd.vsock_cid, f"[{vm.machine_name}] "), start_virtiofsd(
|
with (
|
||||||
virtiofsd_socket
|
start_waypipe(qemu_cmd.vsock_cid, f"[{vm.machine_name}] "),
|
||||||
|
start_virtiofsd(virtiofsd_socket),
|
||||||
):
|
):
|
||||||
run(
|
run(
|
||||||
nix_shell(packages, qemu_cmd.args),
|
nix_shell(packages, qemu_cmd.args),
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ let
|
|||||||
nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux;
|
nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux;
|
||||||
};
|
};
|
||||||
|
|
||||||
installer = lib.nixosSystem { modules = [ installerModule ]; };
|
installer = lib.nixosSystem {
|
||||||
|
modules = [
|
||||||
|
installerModule
|
||||||
|
{ disko.memSize = 4096; } # FIXME: otherwise the image builder goes OOM
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
clan = self.lib.buildClan {
|
clan = self.lib.buildClan {
|
||||||
clanName = "clan-core";
|
clanName = "clan-core";
|
||||||
|
|||||||
Reference in New Issue
Block a user