diff --git a/nixosModules/clanCore/vm.nix b/nixosModules/clanCore/vm.nix index d9d0a9516..acd57c15f 100644 --- a/nixosModules/clanCore/vm.nix +++ b/nixosModules/clanCore/vm.nix @@ -3,6 +3,14 @@ let vmConfig = extendModules { modules = [ (modulesPath + "/virtualisation/qemu-vm.nix") + { + virtualisation.fileSystems.${config.clanCore.secretsUploadDirectory} = lib.mkForce { + device = "secrets"; + fsType = "9p"; + neededForBoot = true; + options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ]; + }; + } ]; }; in @@ -52,6 +60,8 @@ in toplevel = vmConfig.config.system.build.toplevel; regInfo = (pkgs.closureInfo { rootPaths = vmConfig.config.virtualisation.additionalPaths; }); inherit (config.clan.virtualisation) memorySize cores graphics; + generateSecrets = config.system.clan.generateSecrets; + uploadSecrets = config.system.clan.uploadSecrets; }); }; diff --git a/pkgs/clan-cli/clan_cli/task_manager.py b/pkgs/clan-cli/clan_cli/task_manager.py index d1fa34045..a710b33ca 100644 --- a/pkgs/clan-cli/clan_cli/task_manager.py +++ b/pkgs/clan-cli/clan_cli/task_manager.py @@ -8,7 +8,7 @@ import sys import threading import traceback from enum import Enum -from typing import Any, Iterator, Type, TypeVar +from typing import Any, Iterator, Optional, Type, TypeVar from uuid import UUID, uuid4 from .errors import ClanError @@ -30,7 +30,7 @@ class Command: self._output.put(None) self.done = True - def run(self, cmd: list[str]) -> None: + def run(self, cmd: list[str], env: Optional[dict[str, str]] = None) -> None: self.running = True self.log.debug(f"Running command: {shlex.join(cmd)}") self.p = subprocess.Popen( @@ -38,6 +38,7 @@ class Command: stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", + env=env, ) assert self.p.stdout is not None and self.p.stderr is not None os.set_blocking(self.p.stdout.fileno(), False) diff --git a/pkgs/clan-cli/clan_cli/vms/create.py b/pkgs/clan-cli/clan_cli/vms/create.py index e754703e7..5888ec20b 100644 --- a/pkgs/clan-cli/clan_cli/vms/create.py +++ b/pkgs/clan-cli/clan_cli/vms/create.py @@ -1,32 +1,36 @@ import argparse import asyncio import json +import os import shlex +import sys import tempfile from pathlib import Path from typing import Iterator from uuid import UUID from ..dirs import get_clan_flake_toplevel -from ..nix import nix_build, nix_shell +from ..nix import nix_build, nix_config, nix_shell from ..task_manager import BaseTask, Command, create_task from .inspect import VmConfig, inspect_vm class BuildVmTask(BaseTask): def __init__(self, uuid: UUID, vm: VmConfig) -> None: - super().__init__(uuid, num_cmds=4) + super().__init__(uuid, num_cmds=6) self.vm = vm def get_vm_create_info(self, cmds: Iterator[Command]) -> dict: + config = nix_config() + system = config["system"] + clan_dir = self.vm.flake_url machine = self.vm.flake_attr cmd = next(cmds) cmd.run( nix_build( [ - # f'{clan_dir}#clanInternals.machines."{system}"."{machine}".config.clan.virtualisation.createJSON' # TODO use this - f'{clan_dir}#nixosConfigurations."{machine}".config.system.clan.vm.create' + f'{clan_dir}#clanInternals.machines."{system}"."{machine}".config.system.clan.vm.create' ] ) ) @@ -48,8 +52,29 @@ class BuildVmTask(BaseTask): tmpdir = Path(tmpdir_) xchg_dir = tmpdir / "xchg" xchg_dir.mkdir() + secrets_dir = tmpdir / "secrets" + secrets_dir.mkdir() disk_img = f"{tmpdir_}/disk.img" + env = os.environ.copy() + env["CLAN_DIR"] = str(self.vm.flake_url) + env["PYTHONPATH"] = str( + ":".join(sys.path) + ) # TODO do this in the clanCore module + env["SECRETS_DIR"] = str(secrets_dir) + + cmd = next(cmds) + cmd.run( + [vm_config["generateSecrets"]], + env=env, + ) + + cmd = next(cmds) + cmd.run( + [vm_config["uploadSecrets"]], + env=env, + ) + cmd = next(cmds) cmd.run( nix_shell( @@ -97,6 +122,7 @@ class BuildVmTask(BaseTask): "-virtfs", "local,path=/nix/store,security_model=none,mount_tag=nix-store", "-virtfs", f"local,path={xchg_dir},security_model=none,mount_tag=shared", "-virtfs", f"local,path={xchg_dir},security_model=none,mount_tag=xchg", + "-virtfs", f"local,path={secrets_dir},security_model=none,mount_tag=secrets", "-drive", f'cache=writeback,file={disk_img},format=raw,id=drive1,if=none,index=1,werror=report', "-device", "virtio-blk-pci,bootindex=1,drive=drive1,serial=root", "-device", "virtio-keyboard", diff --git a/pkgs/clan-cli/clan_cli/vms/inspect.py b/pkgs/clan-cli/clan_cli/vms/inspect.py index 9b8559a75..83230add1 100644 --- a/pkgs/clan-cli/clan_cli/vms/inspect.py +++ b/pkgs/clan-cli/clan_cli/vms/inspect.py @@ -6,7 +6,7 @@ from pydantic import BaseModel from ..async_cmd import run from ..dirs import get_clan_flake_toplevel -from ..nix import nix_eval +from ..nix import nix_config, nix_eval class VmConfig(BaseModel): @@ -19,9 +19,11 @@ class VmConfig(BaseModel): async def inspect_vm(flake_url: str, flake_attr: str) -> VmConfig: + config = nix_config() + system = config["system"] cmd = nix_eval( [ - f"{flake_url}#nixosConfigurations.{json.dumps(flake_attr)}.config.system.clan.vm.config" + f'{flake_url}#clanInternals.machines."{system}"."{flake_attr}".config.system.clan.vm.config' ] ) stdout = await run(cmd) diff --git a/pkgs/clan-cli/tests/test_vms_api.py b/pkgs/clan-cli/tests/test_vms_api.py index 32b74576c..02bf655db 100644 --- a/pkgs/clan-cli/tests/test_vms_api.py +++ b/pkgs/clan-cli/tests/test_vms_api.py @@ -1,10 +1,15 @@ import os from pathlib import Path +from typing import TYPE_CHECKING import pytest from api import TestClient +from cli import Cli from httpx import SyncByteStream +if TYPE_CHECKING: + from age_keys import KeyPair + @pytest.mark.impure def test_inspect(api: TestClient, test_flake_with_core: Path) -> None: @@ -33,7 +38,16 @@ def test_incorrect_uuid(api: TestClient) -> None: @pytest.mark.skipif(not os.path.exists("/dev/kvm"), reason="Requires KVM") @pytest.mark.impure -def test_create(api: TestClient, test_flake_with_core: Path) -> None: +def test_create( + api: TestClient, + monkeypatch: pytest.MonkeyPatch, + test_flake_with_core: Path, + age_keys: list["KeyPair"], +) -> None: + monkeypatch.chdir(test_flake_with_core) + monkeypatch.setenv("SOPS_AGE_KEY", age_keys[0].privkey) + cli = Cli() + cli.run(["secrets", "users", "add", "user1", age_keys[0].pubkey]) print(f"flake_url: {test_flake_with_core} ") response = api.post( "/api/vms/create",