diff --git a/pkgs/clan-cli/clan_cli/secrets/sops.py b/pkgs/clan-cli/clan_cli/secrets/sops.py index 213616280..c23f08f2c 100644 --- a/pkgs/clan-cli/clan_cli/secrets/sops.py +++ b/pkgs/clan-cli/clan_cli/secrets/sops.py @@ -23,7 +23,9 @@ class SopsKey: def get_public_key(privkey: str) -> str: cmd = nix_shell(["age"], ["age-keygen", "-y"]) try: - res = subprocess.run(cmd, input=privkey, stdout=subprocess.PIPE, text=True) + res = subprocess.run( + cmd, input=privkey, stdout=subprocess.PIPE, text=True, check=True + ) except subprocess.CalledProcessError as e: raise ClanError( "Failed to get public key for age private key. Is the key malformed?" diff --git a/pkgs/clan-cli/clan_cli/task_manager.py b/pkgs/clan-cli/clan_cli/task_manager.py index ce4664815..b0a96ff3f 100644 --- a/pkgs/clan-cli/clan_cli/task_manager.py +++ b/pkgs/clan-cli/clan_cli/task_manager.py @@ -68,7 +68,7 @@ class Command: while self.p.poll() is None: # Check if stderr is ready to be read from - rlist, _, _ = select.select([self.p.stderr, self.p.stdout], [], [], 0) + rlist, _, _ = select.select([self.p.stderr, self.p.stdout], [], [], 1) for fd in rlist: try: for line in fd: diff --git a/pkgs/clan-cli/clan_cli/vms/mimetypes/applications/mimeapps.list b/pkgs/clan-cli/clan_cli/vms/mimetypes/applications/mimeapps.list new file mode 100644 index 000000000..3986cfc43 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/vms/mimetypes/applications/mimeapps.list @@ -0,0 +1,4 @@ +[Default Applications] +x-scheme-handler/spice=remote-viewer.desktop +x-scheme-handler/spice+unix=remote-viewer.desktop +x-scheme-handler/spice+tls=remote-viewer.desktop diff --git a/pkgs/clan-cli/clan_cli/vms/mimetypes/applications/remote-viewer.desktop b/pkgs/clan-cli/clan_cli/vms/mimetypes/applications/remote-viewer.desktop new file mode 100644 index 000000000..368dc126b --- /dev/null +++ b/pkgs/clan-cli/clan_cli/vms/mimetypes/applications/remote-viewer.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Name=Remote Viewer +Type=Application +Exec=remote-viewer %u diff --git a/pkgs/clan-cli/clan_cli/vms/run.py b/pkgs/clan-cli/clan_cli/vms/run.py index 9b664b19a..e3e9fac5f 100644 --- a/pkgs/clan-cli/clan_cli/vms/run.py +++ b/pkgs/clan-cli/clan_cli/vms/run.py @@ -3,14 +3,13 @@ import asyncio import json import os import shlex -import subprocess import sys import tempfile from collections.abc import Iterator from pathlib import Path -from threading import Condition, Thread from uuid import UUID +from ..dirs import module_root from ..nix import nix_build, nix_config, nix_eval, nix_shell from ..task_manager import BaseTask, Command, create_task from .inspect import VmConfig, inspect_vm @@ -64,10 +63,12 @@ def qemu_command( "-audiodev", "spice,id=audio0", "-device", "intel-hda", "-device", "hda-duplex,audiodev=audio0", + "-vga", "none", + "-device", "virtio-gpu-gl", + "-display", "spice-app,gl=on", "-device", "virtio-serial-pci", "-chardev", "spicevmc,id=vdagent0,name=vdagent", "-device", "virtserialport,chardev=vdagent0,name=com.redhat.spice.0", - "-spice", f"unix=on,addr={spice_socket},disable-ticketing=on", "-device", "qemu-xhci,id=spicepass", "-chardev", "spicevmc,id=usbredirchardev1,name=usbredir", "-device", "usb-redir,chardev=usbredirchardev1,id=usbredirdev1", @@ -86,21 +87,6 @@ def qemu_command( return command -def start_spicy(spice_socket: Path, stop_condition: Condition) -> None: - while not spice_socket.exists(): - with stop_condition: - if stop_condition.wait(0.1): - return - - spicy = nix_shell(["spice-gtk"], ["spicy", f"--uri=spice+unix://{spice_socket}"]) - proc = subprocess.Popen(spicy) - while proc.poll() is None: - with stop_condition: - if stop_condition.wait(0.1): - proc.terminate() - proc.wait() - - class BuildVmTask(BaseTask): def __init__(self, uuid: UUID, vm: VmConfig, nix_options: list[str] = []) -> None: super().__init__(uuid, num_cmds=7) @@ -229,22 +215,24 @@ class BuildVmTask(BaseTask): disk_img=disk_img, spice_socket=spice_socket, ) - stop_condition = Condition() - spice_thread = Thread( - target=start_spicy, args=(spice_socket, stop_condition), name="qemu" - ) - spice_thread.start() print("$ " + shlex.join(qemu_cmd)) - try: - cmd.run(nix_shell(["qemu"], qemu_cmd), name="qemu") - finally: - with stop_condition: - stop_condition.notify() - spice_thread.join() + packages = ["qemu"] + if self.vm.graphics: + packages.append("virt-viewer") + + env = os.environ.copy() + remote_viewer_mimetypes = module_root() / "vms" / "mimetypes" + env[ + "XDG_DATA_DIRS" + ] = f"{remote_viewer_mimetypes}:{env.get('XDG_DATA_DIRS', '')}" + print(env["XDG_DATA_DIRS"]) + cmd.run(nix_shell(packages, qemu_cmd), name="qemu", env=env) -def run_vm(vm: VmConfig, nix_options: list[str] = []) -> BuildVmTask: +def run_vm( + vm: VmConfig, nix_options: list[str] = [], env: dict[str, str] = {} +) -> BuildVmTask: return create_task(BuildVmTask, vm, nix_options) diff --git a/pkgs/clan-cli/tests/command.py b/pkgs/clan-cli/tests/command.py index a5e68796c..f951c8dd5 100644 --- a/pkgs/clan-cli/tests/command.py +++ b/pkgs/clan-cli/tests/command.py @@ -7,7 +7,7 @@ from typing import IO, Any import pytest -_FILE = None | int | IO[Any] +_FILE = None | int | IO[Any] class Command: diff --git a/pkgs/clan-cli/tests/test_config.py b/pkgs/clan-cli/tests/test_config.py index 5451aacce..0b0fe27ce 100644 --- a/pkgs/clan-cli/tests/test_config.py +++ b/pkgs/clan-cli/tests/test_config.py @@ -1,7 +1,7 @@ import json import tempfile from pathlib import Path -from typing import Any +from typing import Any, Optional import pytest from cli import Cli @@ -213,11 +213,11 @@ def test_map_type() -> None: def test_cast() -> None: assert config.cast(value=["true"], type=bool, opt_description="foo-option") is True assert ( - config.cast(value=["null"], type=str | None, opt_description="foo-option") + config.cast(value=["null"], type=Optional[str], opt_description="foo-option") # noqa: UP007 is None ) assert ( - config.cast(value=["bar"], type=str | None, opt_description="foo-option") + config.cast(value=["bar"], type=Optional[str], opt_description="foo-option") # noqa: UP007 == "bar" )