Merge pull request 'clan-cli: clan machines update-hardware-config now uses kexec, and supports non NixOS targets' (#4948) from Qubasa/clan-core:fix_update_hardware_config into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4948
This commit is contained in:
Luis Hebendanz
2025-08-26 10:16:59 +00:00
4 changed files with 63 additions and 33 deletions

View File

@@ -302,7 +302,8 @@
"test-install-machine-without-system",
"-i", ssh_conn.ssh_key,
"--option", "store", os.environ['CLAN_TEST_STORE'],
f"nonrootuser@localhost:{ssh_conn.host_port}"
"--target-host", f"nonrootuser@localhost:{ssh_conn.host_port}",
"--yes"
]
result = subprocess.run(clan_cmd, capture_output=True, cwd=flake_dir)
@@ -326,7 +327,9 @@
"test-install-machine-without-system",
"-i", ssh_conn.ssh_key,
"--option", "store", os.environ['CLAN_TEST_STORE'],
f"nonrootuser@localhost:{ssh_conn.host_port}"
"--target-host",
f"nonrootuser@localhost:{ssh_conn.host_port}",
"--yes"
]
result = subprocess.run(clan_cmd, capture_output=True, cwd=flake_dir)

View File

@@ -92,16 +92,21 @@ Examples:
"update-hardware-config",
help="Generate hardware specifics for a machine",
description="""
Generates hardware specifics for a machine. Such as the host platform, available kernel modules, etc.
This command will use kexec to boot the target into a minimal NixOS environment to gather the hardware information.
If you want to manually ssh into the target after this command use `ssh root@<ip> -i ~/.config/clan/nixos-anywhere/keys/id_ed25519`
The target must be a Linux based system reachable via SSH.
""",
epilog=(
"""
Examples:
$ clan machines update-hardware-config [MACHINE] [TARGET_HOST]
Will generate hardware specifics for the the specified `[TARGET_HOST]` and place the result in hardware.nix for the given machine `[MACHINE]`.
$ clan machines update-hardware-config [MACHINE] --target-host root@<ip>
Will generate the facter.json hardware report for `[TARGET_HOST]` and place the result in facter.json for the given machine `[MACHINE]`.
For more detailed information, visit: https://docs.clan.lol/guides/getting-started/configure/#machine-configuration

View File

@@ -44,6 +44,18 @@ def update_hardware_config_command(args: argparse.Namespace) -> None:
private_key=args.identity_file,
)
if not args.yes:
confirm = (
input(
f"Update hardware configuration for machine '{machine.name}' at '{target_host.target}'? [y/N]: "
)
.strip()
.lower()
)
if confirm not in ("y", "yes"):
log.info("Aborted.")
return
run_machine_hardware_info(opts, target_host)
@@ -56,11 +68,16 @@ def register_update_hardware_config(parser: argparse.ArgumentParser) -> None:
)
add_dynamic_completer(machine_parser, complete_machines)
parser.add_argument(
"target_host",
"--target-host",
type=str,
nargs="?",
help="ssh address to install to in the form of user@host:2222",
)
parser.add_argument(
"-y",
"--yes",
action="store_true",
help="Automatic yes to prompts; assume 'yes' as answer to all prompts and run non-interactively.",
)
parser.add_argument(
"--host-key-check",
choices=list(get_args(HostKeyCheck)),

View File

@@ -6,12 +6,13 @@ from pathlib import Path
from typing import TypedDict
from clan_lib.api import API
from clan_lib.cmd import RunOpts, run
from clan_lib.cmd import Log, RunOpts, run
from clan_lib.dirs import specific_machine_dir
from clan_lib.errors import ClanCmdError, ClanError
from clan_lib.git import commit_file
from clan_lib.machines.machines import Machine
from clan_lib.nix import nix_config, nix_eval
from clan_lib.nix import nix_config, nix_eval, nix_shell
from clan_lib.ssh.create import create_secret_key_nixos_anywhere
from clan_lib.ssh.remote import Remote
log = logging.getLogger(__name__)
@@ -79,41 +80,45 @@ def run_machine_hardware_info(
hw_file = opts.backend.config_path(opts.machine)
hw_file.parent.mkdir(parents=True, exist_ok=True)
if opts.backend == HardwareConfig.NIXOS_FACTER:
config_command = ["nixos-facter"]
else:
config_command = [
"nixos-generate-config",
# Filesystems are managed by disko
"--no-filesystems",
"--show-hardware-config",
cmd = [
"nixos-anywhere",
"--flake",
f"{machine.flake}#{machine.name}",
"--phases",
"kexec",
"--generate-hardware-config",
str(opts.backend.value),
str(opts.backend.config_path(machine)),
]
with target_host.host_connection() as ssh, ssh.become_root() as sudo_ssh:
out = sudo_ssh.run(config_command, opts=RunOpts(check=False))
if out.returncode != 0:
if "nixos-facter" in out.stderr and "not found" in out.stderr:
machine.error(str(out.stderr))
msg = (
"Please use our custom nixos install images from https://github.com/nix-community/nixos-images/releases/tag/nixos-unstable. "
"nixos-factor only works on nixos / clan systems currently."
)
raise ClanError(msg)
if target_host.private_key:
cmd += ["--ssh-option", f"IdentityFile={target_host.private_key}"]
machine.error(str(out))
msg = f"Failed to inspect {opts.machine}. Address: {target_host.target}"
raise ClanError(msg)
if target_host.port:
cmd += ["--ssh-port", str(target_host.port)]
key_pair = create_secret_key_nixos_anywhere()
cmd += ["-i", str(key_pair.private)]
backup_file = None
if hw_file.exists():
backup_file = hw_file.with_suffix(".bak")
hw_file.replace(backup_file)
hw_file.write_text(out.stdout)
cmd += [target_host.target]
cmd = nix_shell(
["nixos-anywhere"],
cmd,
)
run(
cmd,
RunOpts(log=Log.BOTH, prefix=machine.name, needs_user_terminal=True),
)
print(f"Successfully generated: {hw_file}")
# try to evaluate the machine
# If it fails, the hardware-configuration.nix file is invalid
commit_file(
hw_file,
opts.machine.flake.path,