Merge pull request 'hardware-update-split' (#5261) from Qubasa/clan-core:hardware-update-split into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5261
This commit is contained in:
@@ -216,6 +216,22 @@
|
||||
"${../assets/ssh/privkey}"
|
||||
)
|
||||
|
||||
# Run clan install from host using port forwarding
|
||||
clan_cmd = [
|
||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||
"machines",
|
||||
"init-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
|
||||
clan_cmd = [
|
||||
"${self.packages.${pkgs.system}.clan-cli-full}/bin/clan",
|
||||
|
||||
@@ -260,7 +260,7 @@ const CheckHardware = () => {
|
||||
|
||||
try {
|
||||
// TODO: Debounce
|
||||
const call = client.fetch("run_machine_hardware_info", {
|
||||
const call = client.fetch("run_machine_hardware_info_init", {
|
||||
target_host: {
|
||||
address: store.install.targetHost,
|
||||
port,
|
||||
|
||||
@@ -555,8 +555,8 @@ def main() -> None:
|
||||
else:
|
||||
log.error(e) # noqa: TRY400
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt as ex:
|
||||
log.warning("Interrupted by user", exc_info=ex)
|
||||
except KeyboardInterrupt:
|
||||
log.warning("Interrupted by user")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import argparse
|
||||
from .create import register_create_parser
|
||||
from .delete import register_delete_parser
|
||||
from .generations import register_generations_parser
|
||||
from .hardware import register_update_hardware_config
|
||||
from .hardware import register_init_hardware_config, register_update_hardware_config
|
||||
from .install import register_install_parser
|
||||
from .list import register_list_parser
|
||||
from .morph import register_morph_parser
|
||||
@@ -89,8 +89,8 @@ Examples:
|
||||
)
|
||||
register_list_parser(list_parser)
|
||||
|
||||
update_hardware_config_parser = subparser.add_parser(
|
||||
"update-hardware-config",
|
||||
init_hardware_config_parser = subparser.add_parser(
|
||||
"init-hardware-config",
|
||||
help="Generate hardware specifics for a machine",
|
||||
description="""
|
||||
|
||||
@@ -106,7 +106,7 @@ The target must be a Linux based system reachable via SSH.
|
||||
"""
|
||||
Examples:
|
||||
|
||||
$ clan machines update-hardware-config [MACHINE] --target-host root@<ip>
|
||||
$ clan machines init-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
|
||||
@@ -114,6 +114,27 @@ For more detailed information, visit: https://docs.clan.lol/guides/getting-start
|
||||
"""
|
||||
),
|
||||
)
|
||||
register_init_hardware_config(init_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 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
|
||||
|
||||
"""
|
||||
),
|
||||
)
|
||||
register_update_hardware_config(update_hardware_config_parser)
|
||||
|
||||
install_parser = subparser.add_parser(
|
||||
|
||||
@@ -7,7 +7,8 @@ from clan_lib.flake import require_flake
|
||||
from clan_lib.machines.hardware import (
|
||||
HardwareConfig,
|
||||
HardwareGenerateOptions,
|
||||
run_machine_hardware_info,
|
||||
run_machine_hardware_info_init,
|
||||
run_machine_hardware_info_update,
|
||||
)
|
||||
from clan_lib.machines.machines import Machine
|
||||
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.")
|
||||
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:
|
||||
parser.set_defaults(func=update_hardware_config_command)
|
||||
def init_hardware_config_command(args: argparse.Namespace) -> None:
|
||||
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_init(opts, target_host)
|
||||
|
||||
|
||||
def register_init_hardware_config(parser: argparse.ArgumentParser) -> None:
|
||||
parser.set_defaults(func=init_hardware_config_command)
|
||||
machine_parser = parser.add_argument(
|
||||
"machine",
|
||||
help="the name of the machine",
|
||||
@@ -76,7 +117,52 @@ def register_update_hardware_config(parser: argparse.ArgumentParser) -> None:
|
||||
"-y",
|
||||
"--yes",
|
||||
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(
|
||||
"--host-key-check",
|
||||
|
||||
@@ -69,7 +69,7 @@ class HardwareGenerateOptions:
|
||||
|
||||
|
||||
@API.register
|
||||
def run_machine_hardware_info(
|
||||
def run_machine_hardware_info_init(
|
||||
opts: HardwareGenerateOptions,
|
||||
target_host: Remote,
|
||||
) -> HardwareConfig:
|
||||
@@ -157,6 +157,80 @@ def run_machine_hardware_info(
|
||||
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:
|
||||
"""Detect and return the full hardware configuration for the given machine.
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ def upload_sources(machine: Machine, ssh: Host, upload_inputs: bool) -> str:
|
||||
def run_machine_update(
|
||||
machine: Machine,
|
||||
target_host: Remote | LocalHost,
|
||||
build_host: Remote | LocalHost | None,
|
||||
build_host: Remote | LocalHost | None = None,
|
||||
upload_inputs: bool = False,
|
||||
) -> None:
|
||||
"""Update an existing machine using nixos-rebuild or darwin-rebuild.
|
||||
|
||||
@@ -64,6 +64,8 @@ class Remote:
|
||||
def override(
|
||||
self,
|
||||
*,
|
||||
user: str | None = None,
|
||||
address: str | None = None,
|
||||
host_key_check: HostKeyCheck | None = None,
|
||||
private_key: Path | None = None,
|
||||
password: str | None = None,
|
||||
@@ -75,8 +77,8 @@ class Remote:
|
||||
) -> "Remote":
|
||||
"""Returns a new Remote instance with the same data but with a different host_key_check."""
|
||||
return Remote(
|
||||
address=self.address,
|
||||
user=self.user,
|
||||
address=address or self.address,
|
||||
user=user or self.user,
|
||||
command_prefix=command_prefix or self.command_prefix,
|
||||
port=port or self.port,
|
||||
private_key=private_key if private_key is not None else self.private_key,
|
||||
|
||||
Reference in New Issue
Block a user