Merge pull request 'clan-cli: Replace log.info to machine.info if applicable' (#2602) from Qubasa/clan-core:Qubasa-main into main

This commit is contained in:
clan-bot
2024-12-12 14:44:03 +00:00
17 changed files with 126 additions and 93 deletions

View File

@@ -14,7 +14,7 @@ log = logging.getLogger(__name__)
def create_backup(machine: Machine, provider: str | None = None) -> None: def create_backup(machine: Machine, provider: str | None = None) -> None:
log.info(f"creating backup for {machine.name}") machine.info(f"creating backup for {machine.name}")
backup_scripts = json.loads(machine.eval_nix("config.clan.core.backups")) backup_scripts = json.loads(machine.eval_nix("config.clan.core.backups"))
if provider is None: if provider is None:
for provider in backup_scripts["providers"]: for provider in backup_scripts["providers"]:

View File

@@ -24,20 +24,20 @@ def check_secrets(machine: Machine, service: None | str = None) -> bool:
else: else:
secret_name = secret_fact["name"] secret_name = secret_fact["name"]
if not secret_facts_store.exists(service, secret_name): if not secret_facts_store.exists(service, secret_name):
log.info( machine.info(
f"Secret fact '{secret_fact}' for service '{service}' in machine {machine.name} is missing." f"Secret fact '{secret_fact}' for service '{service}' in machine {machine.name} is missing."
) )
missing_secret_facts.append((service, secret_name)) missing_secret_facts.append((service, secret_name))
for public_fact in machine.facts_data[service]["public"]: for public_fact in machine.facts_data[service]["public"]:
if not public_facts_store.exists(service, public_fact): if not public_facts_store.exists(service, public_fact):
log.info( machine.info(
f"Public fact '{public_fact}' for service '{service}' in machine {machine.name} is missing." f"Public fact '{public_fact}' for service '{service}' in machine {machine.name} is missing."
) )
missing_public_facts.append((service, public_fact)) missing_public_facts.append((service, public_fact))
log.debug(f"missing_secret_facts: {missing_secret_facts}") machine.debug(f"missing_secret_facts: {missing_secret_facts}")
log.debug(f"missing_public_facts: {missing_public_facts}") machine.debug(f"missing_public_facts: {missing_public_facts}")
return not (missing_secret_facts or missing_public_facts) return not (missing_secret_facts or missing_public_facts)

View File

@@ -73,7 +73,7 @@ def generate_service_facts(
service_dir = tmpdir / service service_dir = tmpdir / service
# check if all secrets exist and generate them if at least one is missing # check if all secrets exist and generate them if at least one is missing
needs_regeneration = not check_secrets(machine, service=service) needs_regeneration = not check_secrets(machine, service=service)
log.debug(f"{service} needs_regeneration: {needs_regeneration}") machine.debug(f"{service} needs_regeneration: {needs_regeneration}")
if not (needs_regeneration or regenerate): if not (needs_regeneration or regenerate):
return False return False
if not isinstance(machine.flake, Path): if not isinstance(machine.flake, Path):

View File

@@ -15,7 +15,7 @@ class FactStore(FactStoreBase):
self.machine = machine self.machine = machine
self.works_remotely = False self.works_remotely = False
self.dir = vm_state_dir(machine.flake, machine.name) / "facts" self.dir = vm_state_dir(machine.flake, machine.name) / "facts"
log.debug(f"FactStore initialized with dir {self.dir}") machine.debug(f"FactStore initialized with dir {self.dir}")
def exists(self, service: str, name: str) -> bool: def exists(self, service: str, name: str) -> bool:
fact_path = self.dir / service / name fact_path = self.dir / service / name

View File

@@ -16,7 +16,7 @@ def upload_secrets(machine: Machine) -> None:
secret_facts_store = secret_facts_module.SecretStore(machine=machine) secret_facts_store = secret_facts_module.SecretStore(machine=machine)
if not secret_facts_store.needs_upload(): if not secret_facts_store.needs_upload():
log.info("Secrets already uploaded") machine.info("Secrets already uploaded")
return return
with TemporaryDirectory(prefix="facts-upload-") as tempdir: with TemporaryDirectory(prefix="facts-upload-") as tempdir:

View File

@@ -6,12 +6,15 @@ from pathlib import Path
from clan_cli.cmd import Log, RunOpts, run from clan_cli.cmd import Log, RunOpts, run
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
from clan_cli.machines.machines import Machine
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@contextmanager @contextmanager
def pause_automounting(devices: list[Path]) -> Generator[None, None, None]: def pause_automounting(
devices: list[Path], machine: Machine
) -> Generator[None, None, None]:
""" """
Pause automounting on the device for the duration of this context Pause automounting on the device for the duration of this context
manager manager
@@ -30,11 +33,16 @@ def pause_automounting(devices: list[Path]) -> Generator[None, None, None]:
str_devs = [str(dev) for dev in devices] str_devs = [str(dev) for dev in devices]
cmd = ["sudo", str(inhibit_path), "enable", *str_devs] cmd = ["sudo", str(inhibit_path), "enable", *str_devs]
result = run(cmd, RunOpts(log=Log.BOTH, check=False, needs_user_terminal=True)) result = run(
cmd,
RunOpts(
log=Log.BOTH, check=False, needs_user_terminal=True, prefix=machine.name
),
)
if result.returncode != 0: if result.returncode != 0:
log.error("Failed to inhibit automounting") machine.error("Failed to inhibit automounting")
yield None yield None
cmd = ["sudo", str(inhibit_path), "disable", *str_devs] cmd = ["sudo", str(inhibit_path), "disable", *str_devs]
result = run(cmd, RunOpts(log=Log.BOTH, check=False)) result = run(cmd, RunOpts(log=Log.BOTH, check=False, prefix=machine.name))
if result.returncode != 0: if result.returncode != 0:
log.error("Failed to re-enable automounting") machine.error("Failed to re-enable automounting")

View File

@@ -49,7 +49,7 @@ def flash_machine(
extra_args: list[str] | None = None, extra_args: list[str] | None = None,
) -> None: ) -> None:
devices = [Path(disk.device) for disk in disks] devices = [Path(disk.device) for disk in disks]
with pause_automounting(devices): with pause_automounting(devices, machine):
if extra_args is None: if extra_args is None:
extra_args = [] extra_args = []
system_config_nix: dict[str, Any] = {} system_config_nix: dict[str, Any] = {}

View File

@@ -71,7 +71,7 @@ def show_machine_deployment_target(clan_dir: Path, machine_name: str) -> str | N
"--json", "--json",
] ]
) )
proc = run_no_stdout(cmd) proc = run_no_stdout(cmd, RunOpts(prefix=machine_name))
res = proc.stdout.strip() res = proc.stdout.strip()
target_host = json.loads(res) target_host = json.loads(res)
@@ -93,7 +93,7 @@ def show_machine_hardware_platform(clan_dir: Path, machine_name: str) -> str | N
"--json", "--json",
] ]
) )
proc = run_no_stdout(cmd) proc = run_no_stdout(cmd, RunOpts(prefix=machine_name))
res = proc.stdout.strip() res = proc.stdout.strip()
host_platform = json.loads(res) host_platform = json.loads(res)
@@ -160,9 +160,9 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon
*config_command, *config_command,
], ],
) )
out = run(cmd, RunOpts(needs_user_terminal=True)) out = run(cmd, RunOpts(needs_user_terminal=True, prefix=machine.name))
if out.returncode != 0: if out.returncode != 0:
log.error(out) machine.error(str(out))
msg = f"Failed to inspect {opts.machine}. Address: {opts.target_host}" msg = f"Failed to inspect {opts.machine}. Address: {opts.target_host}"
raise ClanError(msg) raise ClanError(msg)

View File

@@ -3,6 +3,7 @@ import importlib
import json import json
import logging import logging
import os import os
import sys
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
@@ -48,12 +49,12 @@ def install_machine(opts: InstallOptions) -> None:
machine.override_target_host = opts.target_host 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}") machine.info(f"installing {machine.name}")
secret_facts_store = secret_facts_module.SecretStore(machine=machine) secret_facts_store = secret_facts_module.SecretStore(machine=machine)
h = machine.target_host h = machine.target_host
target_host = f"{h.user or 'root'}@{h.host}" target_host = f"{h.user or 'root'}@{h.host}"
log.info(f"target host: {target_host}") machine.info(f"target host: {target_host}")
generate_facts([machine]) generate_facts([machine])
generate_vars([machine]) generate_vars([machine])
@@ -103,7 +104,7 @@ def install_machine(opts: InstallOptions) -> None:
] ]
if not machine.can_build_locally or opts.build_on_remote: if not machine.can_build_locally or opts.build_on_remote:
log.info("Architecture mismatch. Building on remote machine") machine.info("Architecture mismatch. Building on remote machine")
cmd.append("--build-on-remote") cmd.append("--build-on-remote")
if machine.target_host.port: if machine.target_host.port:
@@ -119,62 +120,70 @@ def install_machine(opts: InstallOptions) -> None:
["nixpkgs#nixos-anywhere"], ["nixpkgs#nixos-anywhere"],
cmd, cmd,
), ),
RunOpts(log=Log.BOTH), RunOpts(log=Log.BOTH, prefix=machine.name, needs_user_terminal=True),
) )
def install_command(args: argparse.Namespace) -> None: def install_command(args: argparse.Namespace) -> None:
if args.flake is None: try:
msg = "Could not find clan flake toplevel directory" if args.flake is None:
raise ClanError(msg) msg = "Could not find clan flake toplevel directory"
json_ssh_deploy = None raise ClanError(msg)
if args.json: json_ssh_deploy = None
json_file = Path(args.json) if args.json:
if json_file.is_file(): json_file = Path(args.json)
json_ssh_deploy = json.loads(json_file.read_text()) if json_file.is_file():
json_ssh_deploy = json.loads(json_file.read_text())
else:
json_ssh_deploy = json.loads(args.json)
elif args.png:
json_ssh_deploy = json.loads(qrcode_scan(args.png))
if json_ssh_deploy:
target_host = (
f"root@{find_reachable_host_from_deploy_json(json_ssh_deploy)}"
)
password = json_ssh_deploy["pass"]
elif args.target_host:
target_host = args.target_host
password = None
else: else:
json_ssh_deploy = json.loads(args.json) machine = Machine(
elif args.png: name=args.machine, flake=args.flake, nix_options=args.option
json_ssh_deploy = json.loads(qrcode_scan(args.png)) )
target_host = str(machine.target_host)
password = None
if json_ssh_deploy: if args.password:
target_host = f"root@{find_reachable_host_from_deploy_json(json_ssh_deploy)}" password = args.password
password = json_ssh_deploy["pass"]
elif args.target_host:
target_host = args.target_host
password = None
else:
machine = Machine(name=args.machine, flake=args.flake, nix_options=args.option)
target_host = str(machine.target_host)
password = None
if args.password: if not target_host:
password = args.password msg = "No target host provided, please provide a target host."
raise ClanError(msg)
if not target_host: if not args.yes:
msg = "No target host provided, please provide a target host." ask = input(f"Install {args.machine} to {target_host}? [y/N] ")
raise ClanError(msg) if ask != "y":
return None
if not args.yes: return install_machine(
ask = input(f"Install {args.machine} to {target_host}? [y/N] ") InstallOptions(
if ask != "y": flake=args.flake,
return None machine=args.machine,
target_host=target_host,
return install_machine( kexec=args.kexec,
InstallOptions( debug=args.debug,
flake=args.flake, no_reboot=args.no_reboot,
machine=args.machine, json_ssh_deploy=json_ssh_deploy,
target_host=target_host, nix_options=args.option,
kexec=args.kexec, build_on_remote=args.build_on_remote,
debug=args.debug, update_hardware_config=HardwareConfig(args.update_hardware_config),
no_reboot=args.no_reboot, password=password,
json_ssh_deploy=json_ssh_deploy, ),
nix_options=args.option, )
build_on_remote=args.build_on_remote, except KeyboardInterrupt:
update_hardware_config=HardwareConfig(args.update_hardware_config), log.warning("Interrupted by user")
password=password, sys.exit(1)
),
)
def find_reachable_host_from_deploy_json(deploy_json: dict[str, str]) -> str: def find_reachable_host_from_deploy_json(deploy_json: dict[str, str]) -> str:
@@ -252,4 +261,5 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None:
"--png", "--png",
help="specify the json file for ssh data as the qrcode image (generated by starting the clan installer)", help="specify the json file for ssh data as the qrcode image (generated by starting the clan installer)",
) )
parser.set_defaults(func=install_command) parser.set_defaults(func=install_command)

View File

@@ -8,7 +8,7 @@ from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Any, Literal from typing import TYPE_CHECKING, Any, Literal
from clan_cli.clan_uri import FlakeId from clan_cli.clan_uri import FlakeId
from clan_cli.cmd import run_no_stdout from clan_cli.cmd import RunOpts, run_no_stdout
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
from clan_cli.facts import public_modules as facts_public_modules from clan_cli.facts import public_modules as facts_public_modules
from clan_cli.facts import secret_modules as facts_secret_modules from clan_cli.facts import secret_modules as facts_secret_modules
@@ -70,7 +70,8 @@ class Machine:
output = self._eval_cache.get(attr) output = self._eval_cache.get(attr)
if output is None: if output is None:
output = run_no_stdout( output = run_no_stdout(
nix_eval(["--impure", "--expr", attr]) nix_eval(["--impure", "--expr", attr]),
opts=RunOpts(prefix=self.name),
).stdout.strip() ).stdout.strip()
self._eval_cache[attr] = output self._eval_cache[attr] = output
return json.loads(output) return json.loads(output)
@@ -239,7 +240,8 @@ class Machine:
"--expr", "--expr",
f'let x = (builtins.fetchTree {{ type = "file"; url = "file://{config_json.name}"; }}); in {{ narHash = x.narHash; path = x.outPath; }}', f'let x = (builtins.fetchTree {{ type = "file"; url = "file://{config_json.name}"; }}); in {{ narHash = x.narHash; path = x.outPath; }}',
] ]
) ),
opts=RunOpts(prefix=self.name),
).stdout.strip() ).stdout.strip()
) )
@@ -277,9 +279,15 @@ class Machine:
args += nix_options + self.nix_options args += nix_options + self.nix_options
if method == "eval": if method == "eval":
output = run_no_stdout(nix_eval(args)).stdout.strip() output = run_no_stdout(
nix_eval(args), opts=RunOpts(prefix=self.name)
).stdout.strip()
return output return output
return Path(run_no_stdout(nix_build(args)).stdout.strip()) return Path(
run_no_stdout(
nix_build(args), opts=RunOpts(prefix=self.name)
).stdout.strip()
)
def eval_nix( def eval_nix(
self, self,

View File

@@ -170,7 +170,9 @@ def deploy_machine(machines: list[Machine]) -> None:
# if the machine is mobile, we retry to deploy with the mobile workaround method # if the machine is mobile, we retry to deploy with the mobile workaround method
is_mobile = machine.deployment.get("nixosMobileWorkaround", False) is_mobile = machine.deployment.get("nixosMobileWorkaround", False)
if is_mobile and ret.returncode != 0: if is_mobile and ret.returncode != 0:
log.info("Mobile machine detected, applying workaround deployment method") machine.info(
"Mobile machine detected, applying workaround deployment method"
)
ret = host.run( ret = host.run(
test_cmd, test_cmd,
RunOpts(msg_color=MsgColor(stderr=AnsiColor.DEFAULT)), RunOpts(msg_color=MsgColor(stderr=AnsiColor.DEFAULT)),

View File

@@ -77,7 +77,7 @@ class KeyType(enum.Enum):
except FileNotFoundError: except FileNotFoundError:
return return
except Exception as ex: except Exception as ex:
log.warn(f"Could not read age keys from {key_path}: {ex}") log.warning(f"Could not read age keys from {key_path}: {ex}")
# Sops will try every location, see age/keysource.go # Sops will try every location, see age/keysource.go
if key_path := os.environ.get("SOPS_AGE_KEY_FILE"): if key_path := os.environ.get("SOPS_AGE_KEY_FILE"):

View File

@@ -3,7 +3,7 @@ import json
import logging import logging
from pathlib import Path from pathlib import Path
from clan_cli.cmd import run_no_stdout from clan_cli.cmd import RunOpts, run_no_stdout
from clan_cli.completions import ( from clan_cli.completions import (
add_dynamic_completer, add_dynamic_completer,
complete_machines, complete_machines,
@@ -11,12 +11,13 @@ from clan_cli.completions import (
) )
from clan_cli.dirs import get_clan_flake_toplevel_or_env from clan_cli.dirs import get_clan_flake_toplevel_or_env
from clan_cli.errors import ClanCmdError, ClanError from clan_cli.errors import ClanCmdError, ClanError
from clan_cli.machines.machines import Machine
from clan_cli.nix import nix_eval from clan_cli.nix import nix_eval
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def list_state_folders(machine: str, service: None | str = None) -> None: def list_state_folders(machine: Machine, service: None | str = None) -> None:
uri = "TODO" uri = "TODO"
if (clan_dir_result := get_clan_flake_toplevel_or_env()) is not None: if (clan_dir_result := get_clan_flake_toplevel_or_env()) is not None:
flake = clan_dir_result flake = clan_dir_result
@@ -31,7 +32,7 @@ def list_state_folders(machine: str, service: None | str = None) -> None:
res = "{}" res = "{}"
try: try:
proc = run_no_stdout(cmd) proc = run_no_stdout(cmd, opts=RunOpts(prefix=machine.name))
res = proc.stdout.strip() res = proc.stdout.strip()
except ClanCmdError as e: except ClanCmdError as e:
msg = "Clan might not have meta attributes" msg = "Clan might not have meta attributes"
@@ -49,7 +50,7 @@ def list_state_folders(machine: str, service: None | str = None) -> None:
msg = f"Service {service} isn't configured for this machine." msg = f"Service {service} isn't configured for this machine."
raise ClanError( raise ClanError(
msg, msg,
location=f"clan state list {machine} --service {service}", location=f"clan state list {machine.name} --service {service}",
description=f"The service: {service} needs to be configured for the machine.", description=f"The service: {service} needs to be configured for the machine.",
) )
@@ -69,7 +70,9 @@ def list_state_folders(machine: str, service: None | str = None) -> None:
def list_command(args: argparse.Namespace) -> None: def list_command(args: argparse.Namespace) -> None:
list_state_folders(machine=args.machine, service=args.service) list_state_folders(
Machine(name=args.machine, flake=args.flake), service=args.service
)
def register_state_parser(parser: argparse.ArgumentParser) -> None: def register_state_parser(parser: argparse.ArgumentParser) -> None:

View File

@@ -60,7 +60,7 @@ def vars_status(machine: Machine, generator_name: None | str = None) -> VarStatu
if file.secret: if file.secret:
if not secret_vars_store.exists(generator, file.name): if not secret_vars_store.exists(generator, file.name):
log.info( machine.info(
f"Secret var '{file.name}' for service '{generator.name}' in machine {machine.name} is missing." f"Secret var '{file.name}' for service '{generator.name}' in machine {machine.name} is missing."
) )
missing_secret_vars.append(file) missing_secret_vars.append(file)
@@ -70,13 +70,13 @@ def vars_status(machine: Machine, generator_name: None | str = None) -> VarStatu
file_name=file.name, file_name=file.name,
) )
if msg: if msg:
log.info( machine.info(
f"Secret var '{file.name}' for service '{generator.name}' in machine {machine.name} needs update: {msg}" f"Secret var '{file.name}' for service '{generator.name}' in machine {machine.name} needs update: {msg}"
) )
unfixed_secret_vars.append(file) unfixed_secret_vars.append(file)
elif not public_vars_store.exists(generator, file.name): elif not public_vars_store.exists(generator, file.name):
log.info( machine.info(
f"Public var '{file.name}' for service '{generator.name}' in machine {machine.name} is missing." f"Public var '{file.name}' for service '{generator.name}' in machine {machine.name} is missing."
) )
missing_public_vars.append(file) missing_public_vars.append(file)
@@ -86,13 +86,13 @@ def vars_status(machine: Machine, generator_name: None | str = None) -> VarStatu
and public_vars_store.hash_is_valid(generator) and public_vars_store.hash_is_valid(generator)
): ):
invalid_generators.append(generator.name) invalid_generators.append(generator.name)
log.info( machine.info(
f"Generator '{generator.name}' in machine {machine.name} has outdated invalidation hash." f"Generator '{generator.name}' in machine {machine.name} has outdated invalidation hash."
) )
log.debug(f"missing_secret_vars: {missing_secret_vars}") machine.debug(f"missing_secret_vars: {missing_secret_vars}")
log.debug(f"missing_public_vars: {missing_public_vars}") machine.debug(f"missing_public_vars: {missing_public_vars}")
log.debug(f"unfixed_secret_vars: {unfixed_secret_vars}") machine.debug(f"unfixed_secret_vars: {unfixed_secret_vars}")
log.debug(f"invalid_generators: {invalid_generators}") machine.debug(f"invalid_generators: {invalid_generators}")
return VarStatus( return VarStatus(
missing_secret_vars, missing_secret_vars,
missing_public_vars, missing_public_vars,

View File

@@ -283,13 +283,13 @@ def _migration_file_exists(
if is_secret: if is_secret:
if machine.secret_facts_store.exists(generator.name, fact_name): if machine.secret_facts_store.exists(generator.name, fact_name):
return True return True
log.debug( machine.debug(
f"Cannot migrate fact {fact_name} for service {generator.name}, as it does not exist in the secret fact store" f"Cannot migrate fact {fact_name} for service {generator.name}, as it does not exist in the secret fact store"
) )
if not is_secret: if not is_secret:
if machine.public_facts_store.exists(generator.name, fact_name): if machine.public_facts_store.exists(generator.name, fact_name):
return True return True
log.debug( machine.debug(
f"Cannot migrate fact {fact_name} for service {generator.name}, as it does not exist in the public fact store" f"Cannot migrate fact {fact_name} for service {generator.name}, as it does not exist in the public fact store"
) )
return False return False

View File

@@ -19,7 +19,7 @@ class FactStore(StoreBase):
self.machine = machine self.machine = machine
self.works_remotely = False self.works_remotely = False
self.dir = vm_state_dir(machine.flake, machine.name) / "facts" self.dir = vm_state_dir(machine.flake, machine.name) / "facts"
log.debug(f"FactStore initialized with dir {self.dir}") machine.debug(f"FactStore initialized with dir {self.dir}")
@property @property
def store_name(self) -> str: def store_name(self) -> str:

View File

@@ -117,6 +117,7 @@ def prepare_disk(
@contextmanager @contextmanager
def start_vm( def start_vm(
machine: Machine,
args: list[str], args: list[str],
packages: list[str], packages: list[str],
extra_env: dict[str, str], extra_env: dict[str, str],
@@ -127,7 +128,7 @@ def start_vm(
env = os.environ.copy() env = os.environ.copy()
env.update(extra_env) env.update(extra_env)
cmd = nix_shell(packages, args) cmd = nix_shell(packages, args)
log.debug(f"Starting VM with command: {cmd}") machine.debug(f"Starting VM with command: {cmd}")
with subprocess.Popen( with subprocess.Popen(
cmd, env=env, stdout=stdout, stderr=stderr, stdin=stdin cmd, env=env, stdout=stdout, stderr=stderr, stdin=stdin
) as process: ) as process:
@@ -215,7 +216,7 @@ def spawn_vm(
nix_options = [] nix_options = []
with ExitStack() as stack: with ExitStack() as stack:
machine = Machine(name=vm.machine_name, flake=vm.flake_url) machine = Machine(name=vm.machine_name, flake=vm.flake_url)
log.debug(f"Creating VM for {machine}") machine.debug(f"Creating VM for {machine}")
# store the temporary rootfs inside XDG_CACHE_HOME on the host # store the temporary rootfs inside XDG_CACHE_HOME on the host
# otherwise, when using /tmp, we risk running out of memory # otherwise, when using /tmp, we risk running out of memory
@@ -292,6 +293,7 @@ def spawn_vm(
start_waypipe(qemu_cmd.vsock_cid, f"[{vm.machine_name}] "), start_waypipe(qemu_cmd.vsock_cid, f"[{vm.machine_name}] "),
start_virtiofsd(virtiofsd_socket), start_virtiofsd(virtiofsd_socket),
start_vm( start_vm(
machine,
qemu_cmd.args, qemu_cmd.args,
packages, packages,
extra_env, extra_env,