expose an option to generate hardware configuration during installation
This commit is contained in:
@@ -2,13 +2,14 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
from clan_cli.api import API
|
from clan_cli.api import API
|
||||||
from clan_cli.clan_uri import FlakeId
|
from clan_cli.clan_uri import FlakeId
|
||||||
from clan_cli.cmd import run, run_no_stdout
|
from clan_cli.cmd import run, run_no_stdout
|
||||||
from clan_cli.completions import add_dynamic_completer, complete_machines
|
from clan_cli.completions import add_dynamic_completer, complete_machines
|
||||||
|
from clan_cli.dirs import specific_machine_dir
|
||||||
from clan_cli.errors import ClanCmdError, ClanError
|
from clan_cli.errors import ClanCmdError, ClanError
|
||||||
from clan_cli.git import commit_file
|
from clan_cli.git import commit_file
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
@@ -19,33 +20,40 @@ from .types import machine_name_type
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class HardwareConfig(Enum):
|
||||||
class HardwareReport:
|
NIXOS_FACTER = "nixos-facter"
|
||||||
backend: Literal["nixos-generate-config", "nixos-facter"]
|
NIXOS_GENERATE_CONFIG = "nixos-generate-config"
|
||||||
|
NONE = "none"
|
||||||
|
|
||||||
|
def config_path(self, clan_dir: Path, machine_name: str) -> Path:
|
||||||
|
machine_dir = specific_machine_dir(clan_dir, machine_name)
|
||||||
|
if self == HardwareConfig.NIXOS_FACTER:
|
||||||
|
return machine_dir / "facter.json"
|
||||||
|
return machine_dir / "hardware-configuration.nix"
|
||||||
|
|
||||||
hw_nix_file = "hardware-configuration.nix"
|
@classmethod
|
||||||
facter_file = "facter.json"
|
def detect_type(
|
||||||
|
cls: type["HardwareConfig"], clan_dir: Path, machine_name: str
|
||||||
|
) -> "HardwareConfig":
|
||||||
|
hardware_config = HardwareConfig.NIXOS_GENERATE_CONFIG.config_path(
|
||||||
|
clan_dir, machine_name
|
||||||
|
)
|
||||||
|
|
||||||
|
if hardware_config.exists() and "throw" not in hardware_config.read_text():
|
||||||
|
return HardwareConfig.NIXOS_GENERATE_CONFIG
|
||||||
|
|
||||||
|
if HardwareConfig.NIXOS_FACTER.config_path(clan_dir, machine_name).exists():
|
||||||
|
return HardwareConfig.NIXOS_FACTER
|
||||||
|
|
||||||
|
return HardwareConfig.NONE
|
||||||
|
|
||||||
|
|
||||||
@API.register
|
@API.register
|
||||||
def show_machine_hardware_info(
|
def show_machine_hardware_config(clan_dir: Path, machine_name: str) -> HardwareConfig:
|
||||||
clan_dir: Path, machine_name: str
|
|
||||||
) -> HardwareReport | None:
|
|
||||||
"""
|
"""
|
||||||
Show hardware information for a machine returns None if none exist.
|
Show hardware information for a machine returns None if none exist.
|
||||||
"""
|
"""
|
||||||
|
return HardwareConfig.detect_type(clan_dir, machine_name)
|
||||||
hw_file = Path(clan_dir) / "machines" / machine_name / hw_nix_file
|
|
||||||
is_template = hw_file.exists() and "throw" in hw_file.read_text()
|
|
||||||
|
|
||||||
if hw_file.exists() and not is_template:
|
|
||||||
return HardwareReport("nixos-generate-config")
|
|
||||||
|
|
||||||
if Path(f"{clan_dir}/machines/{machine_name}/{facter_file}").exists():
|
|
||||||
return HardwareReport("nixos-facter")
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@API.register
|
@API.register
|
||||||
@@ -96,14 +104,14 @@ def show_machine_hardware_platform(clan_dir: Path, machine_name: str) -> str | N
|
|||||||
class HardwareGenerateOptions:
|
class HardwareGenerateOptions:
|
||||||
flake: FlakeId
|
flake: FlakeId
|
||||||
machine: str
|
machine: str
|
||||||
backend: Literal["nixos-generate-config", "nixos-facter"]
|
backend: HardwareConfig
|
||||||
target_host: str | None = None
|
target_host: str | None = None
|
||||||
keyfile: str | None = None
|
keyfile: str | None = None
|
||||||
password: str | None = None
|
password: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@API.register
|
@API.register
|
||||||
def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareReport:
|
def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareConfig:
|
||||||
"""
|
"""
|
||||||
Generate hardware information for a machine
|
Generate hardware information for a machine
|
||||||
and place the resulting *.nix file in the machine's directory.
|
and place the resulting *.nix file in the machine's directory.
|
||||||
@@ -113,15 +121,10 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareRep
|
|||||||
if opts.target_host is not None:
|
if opts.target_host is not None:
|
||||||
machine.override_target_host = opts.target_host
|
machine.override_target_host = opts.target_host
|
||||||
|
|
||||||
hw_file = opts.flake.path / "machines" / opts.machine
|
hw_file = opts.backend.config_path(opts.flake.path, opts.machine)
|
||||||
if opts.backend == "nixos-generate-config":
|
|
||||||
hw_file /= hw_nix_file
|
|
||||||
else:
|
|
||||||
hw_file /= facter_file
|
|
||||||
|
|
||||||
hw_file.parent.mkdir(parents=True, exist_ok=True)
|
hw_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
if opts.backend == "nixos-facter":
|
if opts.backend == HardwareConfig.NIXOS_FACTER:
|
||||||
config_command = ["nixos-facter"]
|
config_command = ["nixos-facter"]
|
||||||
else:
|
else:
|
||||||
config_command = [
|
config_command = [
|
||||||
@@ -196,7 +199,7 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareRep
|
|||||||
description=f"Configuration at '{hw_file}' is invalid. Please check the file and try again.",
|
description=f"Configuration at '{hw_file}' is invalid. Please check the file and try again.",
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
return HardwareReport(opts.backend)
|
return opts.backend
|
||||||
|
|
||||||
|
|
||||||
def update_hardware_config_command(args: argparse.Namespace) -> None:
|
def update_hardware_config_command(args: argparse.Namespace) -> None:
|
||||||
@@ -205,7 +208,7 @@ def update_hardware_config_command(args: argparse.Namespace) -> None:
|
|||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
target_host=args.target_host,
|
target_host=args.target_host,
|
||||||
password=args.password,
|
password=args.password,
|
||||||
backend=args.backend,
|
backend=HardwareConfig(args.backend),
|
||||||
)
|
)
|
||||||
generate_machine_hardware_info(opts)
|
generate_machine_hardware_info(opts)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from clan_cli.clan_uri import FlakeId
|
|||||||
from clan_cli.cmd import Log, run
|
from clan_cli.cmd import Log, run
|
||||||
from clan_cli.completions import add_dynamic_completer, complete_machines
|
from clan_cli.completions import add_dynamic_completer, complete_machines
|
||||||
from clan_cli.facts.generate import generate_facts
|
from clan_cli.facts.generate import generate_facts
|
||||||
|
from clan_cli.machines.hardware import HardwareConfig
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
from clan_cli.nix import nix_shell
|
from clan_cli.nix import nix_shell
|
||||||
from clan_cli.ssh.cli import is_ipv6, is_reachable, qrcode_scan
|
from clan_cli.ssh.cli import is_ipv6, is_reachable, qrcode_scan
|
||||||
@@ -24,17 +25,27 @@ class ClanError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def install_nixos(
|
@dataclass
|
||||||
machine: Machine,
|
class InstallOptions:
|
||||||
kexec: str | None = None,
|
# flake to install
|
||||||
debug: bool = False,
|
flake: FlakeId
|
||||||
password: str | None = None,
|
machine: str
|
||||||
no_reboot: bool = False,
|
target_host: str
|
||||||
extra_args: list[str] | None = None,
|
kexec: str | None = None
|
||||||
build_on_remote: bool = False,
|
debug: bool = False
|
||||||
) -> None:
|
no_reboot: bool = False
|
||||||
if extra_args is None:
|
json_ssh_deploy: dict[str, str] | None = None
|
||||||
extra_args = []
|
build_on_remote: bool = False
|
||||||
|
nix_options: list[str] = field(default_factory=list)
|
||||||
|
update_hardware_config: HardwareConfig = HardwareConfig.NONE
|
||||||
|
password: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@API.register
|
||||||
|
def install_machine(opts: InstallOptions) -> None:
|
||||||
|
machine = Machine(opts.machine, flake=opts.flake)
|
||||||
|
machine.override_target_host = opts.target_host
|
||||||
|
|
||||||
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
secret_facts_module = importlib.import_module(machine.secret_facts_module)
|
||||||
log.info(f"installing {machine.name}")
|
log.info(f"installing {machine.name}")
|
||||||
secret_facts_store = secret_facts_module.SecretStore(machine=machine)
|
secret_facts_store = secret_facts_module.SecretStore(machine=machine)
|
||||||
@@ -56,8 +67,8 @@ def install_nixos(
|
|||||||
upload_dir.mkdir(parents=True)
|
upload_dir.mkdir(parents=True)
|
||||||
secret_facts_store.upload(upload_dir)
|
secret_facts_store.upload(upload_dir)
|
||||||
|
|
||||||
if password:
|
if opts.password:
|
||||||
os.environ["SSHPASS"] = password
|
os.environ["SSHPASS"] = opts.password
|
||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"nixos-anywhere",
|
"nixos-anywhere",
|
||||||
@@ -65,16 +76,28 @@ def install_nixos(
|
|||||||
f"{machine.flake}#{machine.name}",
|
f"{machine.flake}#{machine.name}",
|
||||||
"--extra-files",
|
"--extra-files",
|
||||||
str(tmpdir),
|
str(tmpdir),
|
||||||
*extra_args,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if no_reboot:
|
if opts.no_reboot:
|
||||||
cmd.append("--no-reboot")
|
cmd.append("--no-reboot")
|
||||||
|
|
||||||
if build_on_remote:
|
if opts.build_on_remote:
|
||||||
cmd.append("--build-on-remote")
|
cmd.append("--build-on-remote")
|
||||||
|
|
||||||
if password:
|
if opts.update_hardware_config is not HardwareConfig.NONE:
|
||||||
|
cmd.extend(
|
||||||
|
[
|
||||||
|
"--generate-hardware-config",
|
||||||
|
str(opts.update_hardware_config),
|
||||||
|
str(
|
||||||
|
opts.update_hardware_config.config_path(
|
||||||
|
opts.flake.path, machine.name
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if opts.password:
|
||||||
cmd += [
|
cmd += [
|
||||||
"--env-password",
|
"--env-password",
|
||||||
"--ssh-option",
|
"--ssh-option",
|
||||||
@@ -83,9 +106,9 @@ def install_nixos(
|
|||||||
|
|
||||||
if machine.target_host.port:
|
if machine.target_host.port:
|
||||||
cmd += ["--ssh-port", str(machine.target_host.port)]
|
cmd += ["--ssh-port", str(machine.target_host.port)]
|
||||||
if kexec:
|
if opts.kexec:
|
||||||
cmd += ["--kexec", kexec]
|
cmd += ["--kexec", opts.kexec]
|
||||||
if debug:
|
if opts.debug:
|
||||||
cmd.append("--debug")
|
cmd.append("--debug")
|
||||||
cmd.append(target_host)
|
cmd.append(target_host)
|
||||||
|
|
||||||
@@ -98,37 +121,10 @@ def install_nixos(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class InstallOptions:
|
|
||||||
# flake to install
|
|
||||||
flake: FlakeId
|
|
||||||
machine: str
|
|
||||||
target_host: str
|
|
||||||
kexec: str | None = None
|
|
||||||
debug: bool = False
|
|
||||||
no_reboot: bool = False
|
|
||||||
json_ssh_deploy: dict[str, str] | None = None
|
|
||||||
build_on_remote: bool = False
|
|
||||||
nix_options: list[str] = field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
@API.register
|
|
||||||
def install_machine(opts: InstallOptions, password: str | None) -> None:
|
|
||||||
machine = Machine(opts.machine, flake=opts.flake)
|
|
||||||
machine.override_target_host = opts.target_host
|
|
||||||
|
|
||||||
install_nixos(
|
|
||||||
machine,
|
|
||||||
kexec=opts.kexec,
|
|
||||||
debug=opts.debug,
|
|
||||||
password=password,
|
|
||||||
no_reboot=opts.no_reboot,
|
|
||||||
extra_args=opts.nix_options,
|
|
||||||
build_on_remote=opts.build_on_remote,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def install_command(args: argparse.Namespace) -> None:
|
def install_command(args: argparse.Namespace) -> None:
|
||||||
|
if args.flake is None:
|
||||||
|
msg = "Could not find clan flake toplevel directory"
|
||||||
|
raise ClanError(msg)
|
||||||
json_ssh_deploy = None
|
json_ssh_deploy = None
|
||||||
if args.json:
|
if args.json:
|
||||||
json_file = Path(args.json)
|
json_file = Path(args.json)
|
||||||
@@ -166,8 +162,9 @@ def install_command(args: argparse.Namespace) -> None:
|
|||||||
json_ssh_deploy=json_ssh_deploy,
|
json_ssh_deploy=json_ssh_deploy,
|
||||||
nix_options=args.option,
|
nix_options=args.option,
|
||||||
build_on_remote=args.build_on_remote,
|
build_on_remote=args.build_on_remote,
|
||||||
|
update_hardware_config=HardwareConfig(args.update_hardware_config),
|
||||||
|
password=password,
|
||||||
),
|
),
|
||||||
password,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -211,6 +208,13 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
help="do not ask for confirmation",
|
help="do not ask for confirmation",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--update-hardware-config",
|
||||||
|
type=str,
|
||||||
|
default="none",
|
||||||
|
help="update the hardware configuration",
|
||||||
|
choices=[x.value for x in HardwareConfig],
|
||||||
|
)
|
||||||
|
|
||||||
machines_parser = parser.add_argument(
|
machines_parser = parser.add_argument(
|
||||||
"machine",
|
"machine",
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
target_host: info?.deploy.targetHost,
|
target_host: info?.deploy.targetHost,
|
||||||
debug: true,
|
debug: true,
|
||||||
nix_options: [],
|
nix_options: [],
|
||||||
|
password: null,
|
||||||
},
|
},
|
||||||
password: null,
|
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
loading: "Installing...",
|
loading: "Installing...",
|
||||||
|
|||||||
@@ -76,12 +76,12 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const curr = activeURI();
|
const curr = activeURI();
|
||||||
if (curr && props.name) {
|
if (curr && props.name) {
|
||||||
const result = await callApi("show_machine_hardware_info", {
|
const result = await callApi("show_machine_hardware_config", {
|
||||||
clan_dir: curr,
|
clan_dir: curr,
|
||||||
machine_name: props.name,
|
machine_name: props.name,
|
||||||
});
|
});
|
||||||
if (result.status === "error") throw new Error("Failed to fetch data");
|
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||||
return result.data?.backend === "nixos-facter" || null;
|
return result.data === "NIXOS_FACTER" || null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -107,8 +107,8 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
},
|
},
|
||||||
machine: props.name,
|
machine: props.name,
|
||||||
target_host: props.targetHost,
|
target_host: props.targetHost,
|
||||||
|
password: "",
|
||||||
},
|
},
|
||||||
password: "",
|
|
||||||
});
|
});
|
||||||
toast.dismiss(loading_toast);
|
toast.dismiss(loading_toast);
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
machine: props.name,
|
machine: props.name,
|
||||||
keyfile: props.sshKey?.name,
|
keyfile: props.sshKey?.name,
|
||||||
target_host: props.targetHost,
|
target_host: props.targetHost,
|
||||||
backend: "nixos-facter",
|
backend: "NIXOS_FACTER",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
toast.dismiss(loading_toast);
|
toast.dismiss(loading_toast);
|
||||||
|
|||||||
Reference in New Issue
Block a user