From ab2defa9e43c7e71eb1fe27c3a0cb3ac704b9263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 6 Mar 2024 11:27:26 +0100 Subject: [PATCH 1/7] add confirmation prompt when installing --- checks/installation/flake-module.nix | 2 +- pkgs/clan-cli/clan_cli/machines/install.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/checks/installation/flake-module.nix b/checks/installation/flake-module.nix index 0768ad4f..75ba9997 100644 --- a/checks/installation/flake-module.nix +++ b/checks/installation/flake-module.nix @@ -107,7 +107,7 @@ in 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.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: target.shutdown() except BrokenPipeError: diff --git a/pkgs/clan-cli/clan_cli/machines/install.py b/pkgs/clan-cli/clan_cli/machines/install.py index 701303fe..82566adf 100644 --- a/pkgs/clan-cli/clan_cli/machines/install.py +++ b/pkgs/clan-cli/clan_cli/machines/install.py @@ -63,6 +63,7 @@ class InstallOptions: machine: str target_host: str kexec: str | None + confirm: bool def install_command(args: argparse.Namespace) -> None: @@ -71,10 +72,16 @@ def install_command(args: argparse.Namespace) -> None: machine=args.machine, target_host=args.target_host, kexec=args.kexec, + confirm=not args.yes, ) machine = Machine(opts.machine, flake=opts.flake) 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) @@ -84,6 +91,12 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None: type=str, 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( "machine", type=str, From dd73406a9239b3533a79a5094347ef6c221e5505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 6 Mar 2024 16:41:26 +0100 Subject: [PATCH 2/7] installer: switch to systemd-boot grub is not able to boot from the disks that we flash for weird reasons. Since BIOS-boot is on life-support, we may as well just use systemd-boot. --- nixosModules/installer/default.nix | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/nixosModules/installer/default.nix b/nixosModules/installer/default.nix index 379fbcd5..5d00cd85 100644 --- a/nixosModules/installer/default.nix +++ b/nixosModules/installer/default.nix @@ -48,8 +48,13 @@ cat /var/shared/qrcode.utf8 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 = { disk = { stick = { @@ -59,10 +64,10 @@ content = { type = "gpt"; partitions = { - boot = { - size = "1M"; - type = "EF02"; # for grub MBR - }; + #boot = { + # size = "1M"; + # type = "EF02"; # for grub MBR + #}; ESP = { size = "100M"; type = "EF00"; From f599243cbd76c69b6fc8710cb4e3b0f59f59a01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 7 Mar 2024 13:30:53 +0100 Subject: [PATCH 3/7] add flash command --- checks/flake-module.nix | 1 + checks/flash/flake-module.nix | 46 ++++++++++++ flake.lock | 6 +- pkgs/clan-cli/clan_cli/flash.py | 119 +++++++++++++++++++++++++++----- 4 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 checks/flash/flake-module.nix diff --git a/checks/flake-module.nix b/checks/flake-module.nix index 22a5e66d..dcdb0090 100644 --- a/checks/flake-module.nix +++ b/checks/flake-module.nix @@ -3,6 +3,7 @@ ./impure/flake-module.nix ./backups/flake-module.nix ./installation/flake-module.nix + ./flash/flake-module.nix ]; perSystem = { pkgs, lib, self', ... }: { checks = diff --git a/checks/flash/flake-module.nix b/checks/flash/flake-module.nix new file mode 100644 index 00000000..3d729fcd --- /dev/null +++ b/checks/flash/flake-module.nix @@ -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; }; + }; + }; +} diff --git a/flake.lock b/flake.lock index a34c6dd2..30440e79 100644 --- a/flake.lock +++ b/flake.lock @@ -78,11 +78,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708847675, - "narHash": "sha256-RUZ7KEs/a4EzRELYDGnRB6i7M1Izii3JD/LyzH0c6Tg=", + "lastModified": 1709764733, + "narHash": "sha256-GptBnEUy8IcRrnd8X5WBJPDXG7M4bjj8OG4Wjg8dCDs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2a34566b67bef34c551f204063faeecc444ae9da", + "rev": "edf9f14255a7ac20f8da7b70609e980a964fca7a", "type": "github" }, "original": { diff --git a/pkgs/clan-cli/clan_cli/flash.py b/pkgs/clan-cli/clan_cli/flash.py index e99edb76..d15df36b 100644 --- a/pkgs/clan-cli/clan_cli/flash.py +++ b/pkgs/clan-cli/clan_cli/flash.py @@ -1,51 +1,110 @@ import argparse import importlib import logging +import os +import shlex +import shutil +from collections.abc import Sequence from dataclasses import dataclass from pathlib import Path from tempfile import TemporaryDirectory +from typing import Any +from .cmd import Log, run +from .errors import ClanError 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__) -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) - secret_store = secrets_module.SecretStore(machine=machine) - - generate_secrets(machine) - + secret_store: SecretStoreBase = secrets_module.SecretStore(machine=machine) with TemporaryDirectory() as tmpdir_: tmpdir = Path(tmpdir_) - upload_dir_ = machine.secrets_upload_directory + upload_dir = machine.secrets_upload_directory - if upload_dir_.startswith("/"): - upload_dir_ = upload_dir_[1:] - upload_dir = tmpdir / upload_dir_ - upload_dir.mkdir(parents=True) - secret_store.upload(upload_dir) + if upload_dir.startswith("/"): + local_dir = tmpdir / upload_dir[1:] + else: + local_dir = tmpdir / upload_dir - fs_image = machine.build_nix("config.system.clan.iso") - print(fs_image) + local_dir.mkdir(parents=True) + 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 class FlashOptions: flake: Path 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: opts = FlashOptions( flake=args.flake, 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) - 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: @@ -55,8 +114,30 @@ def register_parser(parser: argparse.ArgumentParser) -> None: help="machine to install", ) parser.add_argument( - "--device", + "--disk", 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) From 93afd06bcbff457dbccbf664dd537754dc8134bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 7 Mar 2024 13:43:13 +0100 Subject: [PATCH 4/7] fix install test --- checks/installation/flake-module.nix | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/checks/installation/flake-module.nix b/checks/installation/flake-module.nix index 75ba9997..8a402b1b 100644 --- a/checks/installation/flake-module.nix +++ b/checks/installation/flake-module.nix @@ -21,12 +21,6 @@ in (modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests (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"; environment.etc."install-successful".text = "ok"; @@ -92,16 +86,16 @@ in testScript = '' def create_test_machine(oldmachine=None, args={}): # taken from + 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({ - "qemuFlags": - '-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', + "startCommand": startCommand, } | args) driver.machines.append(machine) return machine - start_all() client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519") From 26dd962799552c5312b38cf9c56440ac316c9edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 7 Mar 2024 13:53:00 +0100 Subject: [PATCH 5/7] treefmt --- pkgs/clan-cli/clan_cli/vms/run.py | 11 ++++++----- pkgs/clan-cli/qemu/qmp.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/vms/run.py b/pkgs/clan-cli/clan_cli/vms/run.py index b4751cb4..6b35f98d 100644 --- a/pkgs/clan-cli/clan_cli/vms/run.py +++ b/pkgs/clan-cli/clan_cli/vms/run.py @@ -174,12 +174,13 @@ def run_vm( if vm.graphics and not vm.waypipe: packages.append("nixpkgs#virt-viewer") remote_viewer_mimetypes = module_root() / "vms" / "mimetypes" - env[ - "XDG_DATA_DIRS" - ] = f"{remote_viewer_mimetypes}:{env.get('XDG_DATA_DIRS', '')}" + env["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( - virtiofsd_socket + with ( + start_waypipe(qemu_cmd.vsock_cid, f"[{vm.machine_name}] "), + start_virtiofsd(virtiofsd_socket), ): run( nix_shell(packages, qemu_cmd.args), diff --git a/pkgs/clan-cli/qemu/qmp.py b/pkgs/clan-cli/qemu/qmp.py index 6dbef7c6..0e878f6c 100644 --- a/pkgs/clan-cli/qemu/qmp.py +++ b/pkgs/clan-cli/qemu/qmp.py @@ -1,6 +1,6 @@ # mypy: ignore-errors -""" QEMU Monitor Protocol Python class """ +"""QEMU Monitor Protocol Python class""" # Copyright (C) 2009, 2010 Red Hat Inc. # # Authors: From 3cc97ebc569fa29b0c78fcb1ca91b6625b949efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 7 Mar 2024 14:03:41 +0100 Subject: [PATCH 6/7] fix container tests --- checks/lib/container-driver/test_driver/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/lib/container-driver/test_driver/__init__.py b/checks/lib/container-driver/test_driver/__init__.py index 95c16eb0..be9673a3 100644 --- a/checks/lib/container-driver/test_driver/__init__.py +++ b/checks/lib/container-driver/test_driver/__init__.py @@ -258,7 +258,7 @@ class Driver: self.machines = [] 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: raise ValueError(f"Unable to extract hostname from {container.name}") name = name_match.group(1) From 4dfe4ecfa616cba679e68e966a7293220fc77df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 7 Mar 2024 17:24:57 +0100 Subject: [PATCH 7/7] fix building installer iso --- pkgs/installer/flake-module.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/installer/flake-module.nix b/pkgs/installer/flake-module.nix index 0e985378..e9d8c875 100644 --- a/pkgs/installer/flake-module.nix +++ b/pkgs/installer/flake-module.nix @@ -12,7 +12,12 @@ let 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 { clanName = "clan-core";