Merge remote-tracking branch 'origin/main' into rework-installation
This commit is contained in:
@@ -6,7 +6,6 @@ from .delete import register_delete_parser
|
||||
from .hardware import register_hw_generate
|
||||
from .install import register_install_parser
|
||||
from .list import register_list_parser
|
||||
from .show import register_show_parser
|
||||
from .update import register_update_parser
|
||||
|
||||
|
||||
@@ -86,17 +85,6 @@ For more detailed information, visit: https://docs.clan.lol/getting-started/conf
|
||||
)
|
||||
register_hw_generate(generate_hw_parser)
|
||||
|
||||
show_parser = subparser.add_parser(
|
||||
"show",
|
||||
help="Show a machine",
|
||||
epilog=(
|
||||
"""
|
||||
This subcommand shows the details of a machine managed by this clan like icon, description, etc
|
||||
"""
|
||||
),
|
||||
)
|
||||
register_show_parser(show_parser)
|
||||
|
||||
install_parser = subparser.add_parser(
|
||||
"install",
|
||||
help="Install a machine",
|
||||
|
||||
@@ -31,6 +31,8 @@ def create_machine(flake: FlakeId, machine: Machine) -> None:
|
||||
if machine.name in full_inventory.machines.keys():
|
||||
raise ClanError(f"Machine with the name {machine.name} already exists")
|
||||
|
||||
print(f"Define machine {machine.name}", machine)
|
||||
|
||||
inventory.machines.update({machine.name: machine})
|
||||
save_inventory(inventory, flake.path, f"Create machine {machine.name}")
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ def generate_machine_hardware_info(
|
||||
machine_name: str,
|
||||
hostname: str | None = None,
|
||||
password: str | None = None,
|
||||
keyfile: str | None = None,
|
||||
force: bool | None = False,
|
||||
) -> HardwareInfo:
|
||||
"""
|
||||
@@ -117,15 +118,15 @@ def generate_machine_hardware_info(
|
||||
[
|
||||
*(["sshpass", "-p", f"{password}"] if password else []),
|
||||
"ssh",
|
||||
# Disable strict host key checking
|
||||
"-o",
|
||||
"StrictHostKeyChecking=no",
|
||||
*(["-i", f"{keyfile}"] if keyfile else []),
|
||||
# Disable known hosts file
|
||||
"-o",
|
||||
"UserKnownHostsFile=/dev/null",
|
||||
"-p",
|
||||
str(machine.target_host.port),
|
||||
target_host,
|
||||
"-o UserKnownHostsFile=/dev/null",
|
||||
f"{hostname}",
|
||||
"nixos-generate-config",
|
||||
# Filesystems are managed by disko
|
||||
"--no-filesystems",
|
||||
|
||||
@@ -3,10 +3,12 @@ import importlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from clan_cli.api import API
|
||||
|
||||
from ..clan_uri import FlakeId
|
||||
from ..cmd import Log, run
|
||||
from ..completions import add_dynamic_completer, complete_machines
|
||||
@@ -91,15 +93,30 @@ def install_nixos(
|
||||
|
||||
@dataclass
|
||||
class InstallOptions:
|
||||
# flake to install
|
||||
flake: FlakeId
|
||||
machine: str
|
||||
target_host: str
|
||||
kexec: str | None
|
||||
confirm: bool
|
||||
debug: bool
|
||||
no_reboot: bool
|
||||
json_ssh_deploy: dict[str, str] | None
|
||||
nix_options: list[str]
|
||||
kexec: str | None = None
|
||||
debug: bool = False
|
||||
no_reboot: bool = False
|
||||
json_ssh_deploy: dict[str, str] | None = None
|
||||
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.target_host_address = opts.target_host
|
||||
|
||||
install_nixos(
|
||||
machine,
|
||||
kexec=opts.kexec,
|
||||
debug=opts.debug,
|
||||
password=password,
|
||||
no_reboot=opts.no_reboot,
|
||||
extra_args=opts.nix_options,
|
||||
)
|
||||
|
||||
|
||||
def install_command(args: argparse.Namespace) -> None:
|
||||
@@ -123,32 +140,23 @@ def install_command(args: argparse.Namespace) -> None:
|
||||
target_host = args.target_host
|
||||
password = None
|
||||
|
||||
opts = InstallOptions(
|
||||
flake=args.flake,
|
||||
machine=args.machine,
|
||||
target_host=target_host,
|
||||
kexec=args.kexec,
|
||||
confirm=not args.yes,
|
||||
debug=args.debug,
|
||||
no_reboot=args.no_reboot,
|
||||
json_ssh_deploy=json_ssh_deploy,
|
||||
nix_options=args.option,
|
||||
)
|
||||
machine = Machine(opts.machine, flake=opts.flake)
|
||||
machine.target_host_address = opts.target_host
|
||||
|
||||
if opts.confirm:
|
||||
ask = input(f"Install {machine.name} to {opts.target_host}? [y/N] ")
|
||||
if not args.yes:
|
||||
ask = input(f"Install {args.machine} to {target_host}? [y/N] ")
|
||||
if ask != "y":
|
||||
return
|
||||
|
||||
install_nixos(
|
||||
machine,
|
||||
kexec=opts.kexec,
|
||||
debug=opts.debug,
|
||||
password=password,
|
||||
no_reboot=opts.no_reboot,
|
||||
extra_args=opts.nix_options,
|
||||
return install_machine(
|
||||
InstallOptions(
|
||||
flake=args.flake,
|
||||
machine=args.machine,
|
||||
target_host=target_host,
|
||||
kexec=args.kexec,
|
||||
debug=args.debug,
|
||||
no_reboot=args.no_reboot,
|
||||
json_ssh_deploy=json_ssh_deploy,
|
||||
nix_options=args.option,
|
||||
),
|
||||
password,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,121 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from clan_cli.api import API
|
||||
from clan_cli.cmd import run_no_stdout
|
||||
from clan_cli.errors import ClanCmdError, ClanError
|
||||
from clan_cli.inventory import Machine, load_inventory_eval
|
||||
from clan_cli.nix import nix_eval, nix_shell
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@API.register
|
||||
def list_machines(flake_url: str | Path, debug: bool = False) -> dict[str, Machine]:
|
||||
def list_inventory_machines(flake_url: str | Path) -> dict[str, Machine]:
|
||||
inventory = load_inventory_eval(flake_url)
|
||||
return inventory.machines
|
||||
|
||||
|
||||
@dataclass
|
||||
class MachineDetails:
|
||||
machine: Machine
|
||||
has_hw_specs: bool = False
|
||||
# TODO:
|
||||
# has_disk_specs: bool = False
|
||||
|
||||
|
||||
@API.register
|
||||
def get_inventory_machine_details(
|
||||
flake_url: str | Path, machine_name: str
|
||||
) -> MachineDetails:
|
||||
inventory = load_inventory_eval(flake_url)
|
||||
machine = inventory.machines.get(machine_name)
|
||||
if machine is None:
|
||||
raise ClanError(f"Machine {machine_name} not found in inventory")
|
||||
|
||||
hw_config_path = (
|
||||
Path(flake_url) / "machines" / Path(machine_name) / "hardware-configuration.nix"
|
||||
)
|
||||
|
||||
return MachineDetails(
|
||||
machine=machine,
|
||||
has_hw_specs=hw_config_path.exists(),
|
||||
)
|
||||
|
||||
|
||||
@API.register
|
||||
def list_nixos_machines(flake_url: str | Path) -> list[str]:
|
||||
cmd = nix_eval(
|
||||
[
|
||||
f"{flake_url}#nixosConfigurations",
|
||||
"--apply",
|
||||
"builtins.attrNames",
|
||||
"--json",
|
||||
]
|
||||
)
|
||||
proc = run_no_stdout(cmd)
|
||||
|
||||
try:
|
||||
res = proc.stdout.strip()
|
||||
data = json.loads(res)
|
||||
return data
|
||||
except json.JSONDecodeError as e:
|
||||
raise ClanError(f"Error decoding machines from flake: {e}")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConnectionOptions:
|
||||
keyfile: str | None = None
|
||||
timeout: int = 2
|
||||
|
||||
|
||||
@API.register
|
||||
def check_machine_online(
|
||||
flake_url: str | Path, machine_name: str, opts: ConnectionOptions | None
|
||||
) -> Literal["Online", "Offline"]:
|
||||
machine = load_inventory_eval(flake_url).machines.get(machine_name)
|
||||
if not machine:
|
||||
raise ClanError(f"Machine {machine_name} not found in inventory")
|
||||
|
||||
hostname = machine.deploy.targetHost
|
||||
|
||||
if not hostname:
|
||||
raise ClanError(f"Machine {machine_name} does not specify a targetHost")
|
||||
|
||||
timeout = opts.timeout if opts and opts.timeout else 2
|
||||
|
||||
cmd = nix_shell(
|
||||
["nixpkgs#util-linux", *(["nixpkgs#openssh"] if hostname else [])],
|
||||
[
|
||||
"ssh",
|
||||
*(["-i", f"{opts.keyfile}"] if opts and opts.keyfile else []),
|
||||
# Disable strict host key checking
|
||||
"-o StrictHostKeyChecking=no",
|
||||
# Disable known hosts file
|
||||
"-o UserKnownHostsFile=/dev/null",
|
||||
f"-o ConnectTimeout={timeout}",
|
||||
f"{hostname}",
|
||||
"true",
|
||||
"&> /dev/null",
|
||||
],
|
||||
)
|
||||
try:
|
||||
proc = run_no_stdout(cmd)
|
||||
if proc.returncode != 0:
|
||||
return "Offline"
|
||||
|
||||
return "Online"
|
||||
except ClanCmdError:
|
||||
return "Offline"
|
||||
|
||||
|
||||
def list_command(args: argparse.Namespace) -> None:
|
||||
flake_path = args.flake.path
|
||||
for name in list_machines(flake_path, args.debug).keys():
|
||||
for name in list_nixos_machines(flake_path):
|
||||
print(name)
|
||||
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import argparse
|
||||
import dataclasses
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from clan_cli.api import API
|
||||
|
||||
from ..cmd import run_no_stdout
|
||||
from ..completions import add_dynamic_completer, complete_machines
|
||||
from ..nix import nix_config, nix_eval
|
||||
from .types import machine_name_type
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class MachineInfo:
|
||||
machine_name: str
|
||||
machine_description: str | None
|
||||
machine_icon: str | None
|
||||
|
||||
|
||||
@API.register
|
||||
def show_machine(flake_url: str | Path, machine_name: str) -> MachineInfo:
|
||||
config = nix_config()
|
||||
system = config["system"]
|
||||
cmd = nix_eval(
|
||||
[
|
||||
f"{flake_url}#clanInternals.machines.{system}.{machine_name}",
|
||||
"--apply",
|
||||
"machine: { inherit (machine.config.clan.core) machineDescription machineIcon machineName; }",
|
||||
"--json",
|
||||
]
|
||||
)
|
||||
proc = run_no_stdout(cmd)
|
||||
res = proc.stdout.strip()
|
||||
machine = json.loads(res)
|
||||
|
||||
return MachineInfo(
|
||||
machine_name=machine.get("machineName"),
|
||||
machine_description=machine.get("machineDescription", None),
|
||||
machine_icon=machine.get("machineIcon", None),
|
||||
)
|
||||
|
||||
|
||||
def show_command(args: argparse.Namespace) -> None:
|
||||
machine = show_machine(args.flake.path, args.machine)
|
||||
print(f"Name: {machine.machine_name}")
|
||||
print(f"Description: {machine.machine_description or ''}")
|
||||
print(f"Icon: {machine.machine_icon or ''}")
|
||||
|
||||
|
||||
def register_show_parser(parser: argparse.ArgumentParser) -> None:
|
||||
parser.set_defaults(func=show_command)
|
||||
machine_parser = parser.add_argument(
|
||||
"machine", help="the name of the machine", type=machine_name_type
|
||||
)
|
||||
add_dynamic_completer(machine_parser, complete_machines)
|
||||
@@ -5,11 +5,15 @@ import os
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from clan_cli.api import API
|
||||
from clan_cli.clan_uri import FlakeId
|
||||
|
||||
from ..cmd import run
|
||||
from ..completions import add_dynamic_completer, complete_machines
|
||||
from ..errors import ClanError
|
||||
from ..facts.generate import generate_facts
|
||||
from ..facts.upload import upload_secrets
|
||||
from ..inventory import Machine as InventoryMachine
|
||||
from ..machines.machines import Machine
|
||||
from ..nix import nix_command, nix_metadata
|
||||
from ..ssh import HostKeyCheck
|
||||
@@ -81,6 +85,25 @@ def upload_sources(
|
||||
)
|
||||
|
||||
|
||||
@API.register
|
||||
def update_machines(base_path: str, machines: list[InventoryMachine]) -> None:
|
||||
group_machines: list[Machine] = []
|
||||
|
||||
# Convert InventoryMachine to Machine
|
||||
for machine in machines:
|
||||
m = Machine(
|
||||
name=machine.name,
|
||||
flake=FlakeId(base_path),
|
||||
)
|
||||
if not machine.deploy.targetHost:
|
||||
raise ClanError(f"'TargetHost' is not set for machine '{machine.name}'")
|
||||
# Copy targetHost to machine
|
||||
m.target_host_address = machine.deploy.targetHost
|
||||
group_machines.append(m)
|
||||
|
||||
deploy_machine(MachineGroup(group_machines))
|
||||
|
||||
|
||||
def deploy_machine(machines: MachineGroup) -> None:
|
||||
"""
|
||||
Deploy to all hosts in parallel
|
||||
@@ -97,8 +120,10 @@ def deploy_machine(machines: MachineGroup) -> None:
|
||||
generate_vars([machine], None, False)
|
||||
upload_secrets(machine)
|
||||
|
||||
path = upload_sources(".", target)
|
||||
|
||||
path = upload_sources(
|
||||
str(machine.flake.path) if machine.flake.is_local() else machine.flake.url,
|
||||
target,
|
||||
)
|
||||
if host.host_key_check != HostKeyCheck.STRICT:
|
||||
ssh_arg += " -o StrictHostKeyChecking=no"
|
||||
if host.host_key_check == HostKeyCheck.NONE:
|
||||
@@ -109,6 +134,7 @@ def deploy_machine(machines: MachineGroup) -> None:
|
||||
cmd = [
|
||||
"nixos-rebuild",
|
||||
"switch",
|
||||
"--show-trace",
|
||||
"--fast",
|
||||
"--option",
|
||||
"keep-going",
|
||||
|
||||
Reference in New Issue
Block a user