diff --git a/pkgs/clan-cli/clan_cli/machines/hardware.py b/pkgs/clan-cli/clan_cli/machines/hardware.py
index 72138429a..e1cea0ff4 100644
--- a/pkgs/clan-cli/clan_cli/machines/hardware.py
+++ b/pkgs/clan-cli/clan_cli/machines/hardware.py
@@ -2,13 +2,14 @@ import argparse
import json
import logging
from dataclasses import dataclass
+from enum import Enum
from pathlib import Path
-from typing import Literal
from clan_cli.api import API
from clan_cli.clan_uri import FlakeId
from clan_cli.cmd import run, run_no_stdout
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.git import commit_file
from clan_cli.machines.machines import Machine
@@ -19,33 +20,40 @@ from .types import machine_name_type
log = logging.getLogger(__name__)
-@dataclass
-class HardwareReport:
- backend: Literal["nixos-generate-config", "nixos-facter"]
+class HardwareConfig(Enum):
+ NIXOS_FACTER = "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"
-facter_file = "facter.json"
+ @classmethod
+ 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
-def show_machine_hardware_info(
- clan_dir: Path, machine_name: str
-) -> HardwareReport | None:
+def show_machine_hardware_config(clan_dir: Path, machine_name: str) -> HardwareConfig:
"""
Show hardware information for a machine returns None if none exist.
"""
-
- 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
+ return HardwareConfig.detect_type(clan_dir, machine_name)
@API.register
@@ -96,14 +104,14 @@ def show_machine_hardware_platform(clan_dir: Path, machine_name: str) -> str | N
class HardwareGenerateOptions:
flake: FlakeId
machine: str
- backend: Literal["nixos-generate-config", "nixos-facter"]
+ backend: HardwareConfig
target_host: str | None = None
keyfile: str | None = None
password: str | None = None
@API.register
-def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareReport:
+def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareConfig:
"""
Generate hardware information for a machine
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:
machine.override_target_host = opts.target_host
- hw_file = opts.flake.path / "machines" / opts.machine
- if opts.backend == "nixos-generate-config":
- hw_file /= hw_nix_file
- else:
- hw_file /= facter_file
-
+ hw_file = opts.backend.config_path(opts.flake.path, opts.machine)
hw_file.parent.mkdir(parents=True, exist_ok=True)
- if opts.backend == "nixos-facter":
+ if opts.backend == HardwareConfig.NIXOS_FACTER:
config_command = ["nixos-facter"]
else:
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.",
) from e
- return HardwareReport(opts.backend)
+ return opts.backend
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,
target_host=args.target_host,
password=args.password,
- backend=args.backend,
+ backend=HardwareConfig(args.backend),
)
generate_machine_hardware_info(opts)
diff --git a/pkgs/clan-cli/clan_cli/machines/install.py b/pkgs/clan-cli/clan_cli/machines/install.py
index 414e836c7..0375420e8 100644
--- a/pkgs/clan-cli/clan_cli/machines/install.py
+++ b/pkgs/clan-cli/clan_cli/machines/install.py
@@ -12,6 +12,7 @@ from clan_cli.clan_uri import FlakeId
from clan_cli.cmd import Log, run
from clan_cli.completions import add_dynamic_completer, complete_machines
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.nix import nix_shell
from clan_cli.ssh.cli import is_ipv6, is_reachable, qrcode_scan
@@ -24,17 +25,27 @@ class ClanError(Exception):
pass
-def install_nixos(
- machine: Machine,
- kexec: str | None = None,
- debug: bool = False,
- password: str | None = None,
- no_reboot: bool = False,
- extra_args: list[str] | None = None,
- build_on_remote: bool = False,
-) -> None:
- if extra_args is None:
- extra_args = []
+@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)
+ 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)
log.info(f"installing {machine.name}")
secret_facts_store = secret_facts_module.SecretStore(machine=machine)
@@ -56,8 +67,8 @@ def install_nixos(
upload_dir.mkdir(parents=True)
secret_facts_store.upload(upload_dir)
- if password:
- os.environ["SSHPASS"] = password
+ if opts.password:
+ os.environ["SSHPASS"] = opts.password
cmd = [
"nixos-anywhere",
@@ -65,16 +76,28 @@ def install_nixos(
f"{machine.flake}#{machine.name}",
"--extra-files",
str(tmpdir),
- *extra_args,
]
- if no_reboot:
+ if opts.no_reboot:
cmd.append("--no-reboot")
- if build_on_remote:
+ if opts.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 += [
"--env-password",
"--ssh-option",
@@ -83,9 +106,9 @@ def install_nixos(
if machine.target_host.port:
cmd += ["--ssh-port", str(machine.target_host.port)]
- if kexec:
- cmd += ["--kexec", kexec]
- if debug:
+ if opts.kexec:
+ cmd += ["--kexec", opts.kexec]
+ if opts.debug:
cmd.append("--debug")
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:
+ if args.flake is None:
+ msg = "Could not find clan flake toplevel directory"
+ raise ClanError(msg)
json_ssh_deploy = None
if args.json:
json_file = Path(args.json)
@@ -166,8 +162,9 @@ def install_command(args: argparse.Namespace) -> None:
json_ssh_deploy=json_ssh_deploy,
nix_options=args.option,
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",
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(
"machine",
diff --git a/pkgs/webview-ui/app/src/components/MachineListItem.tsx b/pkgs/webview-ui/app/src/components/MachineListItem.tsx
index cf6e6692d..39f3908ab 100644
--- a/pkgs/webview-ui/app/src/components/MachineListItem.tsx
+++ b/pkgs/webview-ui/app/src/components/MachineListItem.tsx
@@ -52,8 +52,8 @@ export const MachineListItem = (props: MachineListItemProps) => {
target_host: info?.deploy.targetHost,
debug: true,
nix_options: [],
+ password: null,
},
- password: null,
}),
{
loading: "Installing...",
diff --git a/pkgs/webview-ui/app/src/routes/machines/details.tsx b/pkgs/webview-ui/app/src/routes/machines/details.tsx
index 3eb690f9c..4262f03a5 100644
--- a/pkgs/webview-ui/app/src/routes/machines/details.tsx
+++ b/pkgs/webview-ui/app/src/routes/machines/details.tsx
@@ -1,10 +1,4 @@
-import {
- callApi,
- ClanService,
- Services,
- SuccessData,
- SuccessQuery,
-} from "@/src/api";
+import { callApi, ClanService, SuccessData, SuccessQuery } from "@/src/api";
import { set_single_disk_id } from "@/src/api/disk";
import { get_iwd_service } from "@/src/api/wifi";
import { activeURI } from "@/src/App";
@@ -17,25 +11,11 @@ import {
createForm,
FieldValues,
getValue,
- reset,
setValue,
} from "@modular-forms/solid";
import { useParams } from "@solidjs/router";
-import {
- createQuery,
- QueryObserver,
- useQueryClient,
-} from "@tanstack/solid-query";
-import {
- createSignal,
- For,
- Show,
- Switch,
- Match,
- JSXElement,
- createEffect,
- createMemo,
-} from "solid-js";
+import { createQuery, useQueryClient } from "@tanstack/solid-query";
+import { createSignal, For, Show, Switch, Match, JSXElement } from "solid-js";
import toast from "solid-toast";
type MachineFormInterface = MachineData & {
@@ -58,6 +38,12 @@ interface InstallMachineProps {
disks: Disks;
}
const InstallMachine = (props: InstallMachineProps) => {
+ const curr = activeURI();
+ const { name } = props;
+ if (!curr || !name) {
+ return No Clan selected;
+ }
+
const diskPlaceholder = "Select the boot disk of the remote machine";
const [formStore, { Form, Field }] = createForm();
@@ -67,23 +53,14 @@ const InstallMachine = (props: InstallMachineProps) => {
const [confirmDisk, setConfirmDisk] = createSignal(!hasDisk());
const hwInfoQuery = createQuery(() => ({
- queryKey: [
- activeURI(),
- "machine",
- props.name,
- "show_machine_hardware_info",
- ],
+ queryKey: [curr, "machine", name, "show_machine_hardware_config"],
queryFn: async () => {
- const curr = activeURI();
- if (curr && props.name) {
- const result = await callApi("show_machine_hardware_info", {
- clan_dir: curr,
- machine_name: props.name,
- });
- if (result.status === "error") throw new Error("Failed to fetch data");
- return result.data?.backend === "nixos-facter" || null;
- }
- return null;
+ const result = await callApi("show_machine_hardware_config", {
+ clan_dir: curr,
+ machine_name: name,
+ });
+ if (result.status === "error") throw new Error("Failed to fetch data");
+ return result.data === "NIXOS_FACTER";
},
}));
@@ -107,8 +84,8 @@ const InstallMachine = (props: InstallMachineProps) => {
},
machine: props.name,
target_host: props.targetHost,
+ password: "",
},
- password: "",
});
toast.dismiss(loading_toast);
@@ -158,7 +135,7 @@ const InstallMachine = (props: InstallMachineProps) => {
machine: props.name,
keyfile: props.sshKey?.name,
target_host: props.targetHost,
- backend: "nixos-facter",
+ backend: "NIXOS_FACTER",
},
});
toast.dismiss(loading_toast);