import json import os import re import subprocess from pathlib import Path from tempfile import NamedTemporaryFile from typing import Optional from clan_cli.dirs import machine_settings_file, nixpkgs_source, specific_machine_dir from clan_cli.errors import ClanError, ClanHttpError from clan_cli.git import commit_file from clan_cli.nix import nix_eval def verify_machine_config( flake_dir: Path, machine_name: str, config: Optional[dict] = None, ) -> Optional[str]: """ Verify that the machine evaluates successfully Returns a tuple of (success, error_message) """ if config is None: config = config_for_machine(flake_dir, machine_name) flake = flake_dir with NamedTemporaryFile(mode="w", dir=flake) as clan_machine_settings_file: json.dump(config, clan_machine_settings_file, indent=2) clan_machine_settings_file.seek(0) env = os.environ.copy() env["CLAN_MACHINE_SETTINGS_FILE"] = clan_machine_settings_file.name cmd = nix_eval( flags=[ "--show-trace", "--impure", # needed to access CLAN_MACHINE_SETTINGS_FILE "--expr", f""" let # hardcoding system for now, not sure where to get it from system = "x86_64-linux"; flake = builtins.getFlake (toString {flake}); clan-core = flake.inputs.clan-core; nixpkgsSrc = flake.inputs.nixpkgs or {nixpkgs_source()}; lib = import (nixpkgsSrc + /lib); pkgs = import nixpkgsSrc {{ inherit system; }}; config = lib.importJSON (builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE"); fakeMachine = pkgs.nixos {{ imports = [ clan-core.nixosModules.clanCore # potentially the config might affect submodule options, # therefore we need to import it config {{clanCore.clanDir = {flake};}} ] # add all clan modules specified via clanImports ++ (map (name: clan-core.clanModules.${{name}}) config.clanImports or []); }}; in fakeMachine.config.system.build.vm.outPath """, ], ) # repro_env_break(work_dir=flake, env=env, cmd=cmd) proc = subprocess.run( cmd, capture_output=True, text=True, cwd=flake, env=env, ) if proc.returncode != 0: return proc.stderr return None def config_for_machine(flake_dir: Path, machine_name: str) -> dict: # read the config from a json file located at {flake}/machines/{machine_name}/settings.json if not specific_machine_dir(flake_dir, machine_name).exists(): raise ClanHttpError( msg=f"Machine {machine_name} not found. Create the machine first`", status_code=404, ) settings_path = machine_settings_file(flake_dir, machine_name) if not settings_path.exists(): return {} with open(settings_path) as f: return json.load(f) def set_config_for_machine(flake_dir: Path, machine_name: str, config: dict) -> None: hostname_regex = r"^(?!-)[A-Za-z0-9-]{1,63}(?