clan-cli: Split up update-hardware info into kexec-hardware-info

This commit is contained in:
Qubasa
2025-09-24 15:47:33 +02:00
parent 5595b2f862
commit ac79bfb35f
5 changed files with 208 additions and 11 deletions

View File

@@ -216,6 +216,22 @@
"${../assets/ssh/privkey}" "${../assets/ssh/privkey}"
) )
# Run clan install from host using port forwarding
clan_cmd = [
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
"machines",
"update-hardware-config",
"--debug",
"--flake", str(flake_dir),
"--yes", "test-install-machine-without-system",
"--host-key-check", "none",
"--target-host", f"nonrootuser@localhost:{ssh_conn.host_port}",
"-i", ssh_conn.ssh_key,
"--option", "store", os.environ['CLAN_TEST_STORE']
]
subprocess.run(clan_cmd, check=True)
# Run clan install from host using port forwarding # Run clan install from host using port forwarding
clan_cmd = [ clan_cmd = [
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan", "${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",

View File

@@ -260,7 +260,7 @@ const CheckHardware = () => {
try { try {
// TODO: Debounce // TODO: Debounce
const call = client.fetch("run_machine_hardware_info", { const call = client.fetch("run_machine_hardware_info_kexec", {
target_host: { target_host: {
address: store.install.targetHost, address: store.install.targetHost,
port, port,

View File

@@ -4,7 +4,7 @@ import argparse
from .create import register_create_parser from .create import register_create_parser
from .delete import register_delete_parser from .delete import register_delete_parser
from .generations import register_generations_parser from .generations import register_generations_parser
from .hardware import register_update_hardware_config from .hardware import register_kexec_hardware_config, register_update_hardware_config
from .install import register_install_parser from .install import register_install_parser
from .list import register_list_parser from .list import register_list_parser
from .morph import register_morph_parser from .morph import register_morph_parser
@@ -89,8 +89,8 @@ Examples:
) )
register_list_parser(list_parser) register_list_parser(list_parser)
update_hardware_config_parser = subparser.add_parser( kexec_hardware_config_parser = subparser.add_parser(
"update-hardware-config", "kexec-hardware-config",
help="Generate hardware specifics for a machine", help="Generate hardware specifics for a machine",
description=""" description="""
@@ -106,7 +106,28 @@ The target must be a Linux based system reachable via SSH.
""" """
Examples: Examples:
$ clan machines update-hardware-config [MACHINE] --target-host root@<ip> $ clan machines kexec-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
"""
),
)
register_kexec_hardware_config(kexec_hardware_config_parser)
update_hardware_config_parser = subparser.add_parser(
"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.
The target must be a Linux based system reachable via SSH
""",
epilog=(
"""
Examples:
$ clan machines kexec-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]`. 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 For more detailed information, visit: https://docs.clan.lol/guides/getting-started/configure/#machine-configuration

View File

@@ -7,7 +7,8 @@ from clan_lib.flake import require_flake
from clan_lib.machines.hardware import ( from clan_lib.machines.hardware import (
HardwareConfig, HardwareConfig,
HardwareGenerateOptions, HardwareGenerateOptions,
run_machine_hardware_info, run_machine_hardware_info_kexec,
run_machine_hardware_info_update,
) )
from clan_lib.machines.machines import Machine from clan_lib.machines.machines import Machine
from clan_lib.machines.suggestions import validate_machine_names from clan_lib.machines.suggestions import validate_machine_names
@@ -56,11 +57,51 @@ def update_hardware_config_command(args: argparse.Namespace) -> None:
log.info("Aborted.") log.info("Aborted.")
return return
run_machine_hardware_info(opts, target_host) run_machine_hardware_info_update(opts, target_host)
def register_update_hardware_config(parser: argparse.ArgumentParser) -> None: def kexec_hardware_config_command(args: argparse.Namespace) -> None:
parser.set_defaults(func=update_hardware_config_command) flake = require_flake(args.flake)
validate_machine_names([args.machine], flake)
machine = Machine(flake=flake, name=args.machine)
opts = HardwareGenerateOptions(
machine=machine,
password=args.password,
backend=HardwareConfig(args.backend),
)
if args.target_host:
target_host = Remote.from_ssh_uri(
machine_name=machine.name,
address=args.target_host,
)
else:
target_host = machine.target_host()
target_host = target_host.override(
host_key_check=args.host_key_check,
private_key=args.identity_file,
)
if not args.yes:
confirm = (
input(
"WARNING: This will reboot the target machine into a temporary NixOS system "
"to gather hardware information. This may disrupt any services running on the machine. "
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_kexec(opts, target_host)
def register_kexec_hardware_config(parser: argparse.ArgumentParser) -> None:
parser.set_defaults(func=kexec_hardware_config_command)
machine_parser = parser.add_argument( machine_parser = parser.add_argument(
"machine", "machine",
help="the name of the machine", help="the name of the machine",
@@ -76,7 +117,52 @@ def register_update_hardware_config(parser: argparse.ArgumentParser) -> None:
"-y", "-y",
"--yes", "--yes",
action="store_true", action="store_true",
help="Automatic yes to prompts; assume 'yes' as answer to all prompts and run non-interactively.", help="do not ask for confirmation.",
)
parser.add_argument(
"--host-key-check",
choices=list(get_args(HostKeyCheck)),
default="ask",
help="Host key (.ssh/known_hosts) check mode.",
)
parser.add_argument(
"--password",
help="Pre-provided password the cli will prompt otherwise if needed.",
type=str,
required=False,
)
parser.add_argument(
"--backend",
help="The type of hardware report to generate.",
choices=["nixos-generate-config", "nixos-facter"],
default="nixos-facter",
)
parser.add_argument(
"-i",
dest="identity_file",
type=Path,
help="specify which SSH private key file to use",
)
def register_update_hardware_config(parser: argparse.ArgumentParser) -> None:
parser.set_defaults(func=update_hardware_config_command)
machine_parser = parser.add_argument(
"machine",
help="the name of the machine",
type=machine_name_type,
)
add_dynamic_completer(machine_parser, complete_machines)
parser.add_argument(
"--target-host",
type=str,
help="ssh address to install to in the form of user@host:2222",
)
parser.add_argument(
"-y",
"--yes",
action="store_true",
help="do not ask for confirmation.",
) )
parser.add_argument( parser.add_argument(
"--host-key-check", "--host-key-check",

View File

@@ -69,7 +69,7 @@ class HardwareGenerateOptions:
@API.register @API.register
def run_machine_hardware_info( def run_machine_hardware_info_kexec(
opts: HardwareGenerateOptions, opts: HardwareGenerateOptions,
target_host: Remote, target_host: Remote,
) -> HardwareConfig: ) -> HardwareConfig:
@@ -157,6 +157,80 @@ def run_machine_hardware_info(
return opts.backend return opts.backend
@API.register
def run_machine_hardware_info_update(
opts: HardwareGenerateOptions,
target_host: Remote,
) -> HardwareConfig:
"""Generate hardware information for a machine
and place the resulting *.nix file in the machine's directory.
"""
machine = opts.machine
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",
]
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)
machine.error(str(out))
msg = f"Failed to inspect {opts.machine}. Address: {target_host.target}"
raise ClanError(msg)
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)
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,
f"machines/{opts.machine.name}/{hw_file.name}: update hardware configuration",
)
try:
get_machine_target_platform(opts.machine)
if backup_file:
backup_file.unlink(missing_ok=True)
except ClanCmdError as e:
log.exception("Failed to evaluate hardware-configuration.nix")
# Restore the backup file
print(f"Restoring backup file {backup_file}")
if backup_file:
backup_file.replace(hw_file)
# TODO: Undo the commit
msg = "Invalid hardware-configuration.nix file"
raise ClanError(
msg,
description=f"Configuration at '{hw_file}' is invalid. Please check the file and try again.",
) from e
return opts.backend
def get_machine_hardware_config(machine: Machine) -> HardwareConfig: def get_machine_hardware_config(machine: Machine) -> HardwareConfig:
"""Detect and return the full hardware configuration for the given machine. """Detect and return the full hardware configuration for the given machine.