Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ test:
@mkdir -p $(ROOT)/macos/.build/module-cache/swift $(ROOT)/macos/.build/module-cache/clang
@cd $(ROOT)/macos && SWIFT_MODULECACHE_PATH=$(ROOT)/macos/.build/module-cache/swift CLANG_MODULE_CACHE_PATH=$(ROOT)/macos/.build/module-cache/clang swift test --disable-sandbox
@$(ROOT)/macos/Tests/qemu-persistent-storage.test.sh
@$(ROOT)/macos/Tests/qemu-guest-agent.test.sh

guest:
@OMARCHY_FORCE_BUILD="$(FORCE)" "$(BUILD_CACHE)" \
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ fingerprint of what it last wrote so the immediate echo is dropped. The marker
is cleared as soon as the other side moves on to new content, and expires after
a couple of seconds regardless, so a genuine repeat of the same content still flows.

Developer launches can set `OMARCHY_QEMU_GUEST_AGENT=1` to add a second private
Unix-socket channel named `org.qemu.guest_agent.0`. The guest package is present,
and systemd starts its static service when that virtio port appears. This channel
can execute commands as root, so release launches leave it disabled and the host
socket exists only inside the launcher's mode-0700 per-boot runtime directory.

When a folder is chosen on the start menu, QEMU exports it over virtio-9p with
`security_model=none`, so every host file operation runs as the Mac user and
the Mac keeps real modes and ownership. A small QEMU patch adds
Expand Down
6 changes: 4 additions & 2 deletions guest/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
"gupnp-igd": "1.6.0-2",
"gvfs": "1.60.2-3",
"gzip": "1.14-2",
"harfbuzz": "14.3.1-1",
"harfbuzz": "14.4.0-1",
"hicolor-icon-theme": "0.18-1",
"hidapi": "0.15.0-1",
"highway": "1.4.0-1",
Expand Down Expand Up @@ -423,6 +423,7 @@
"npth": "1.8-1",
"nspr": "4.40-1",
"nss": "3.128-1",
"numactl": "2.0.19-1",
"ocl-icd": "2.3.5-1",
"omarchy-keyring": "20251027-1",
"onetbb": "2023.1.0-1",
Expand Down Expand Up @@ -472,6 +473,7 @@
"python-gobject": "3.56.3-1",
"python-pyxdg": "0.28-7",
"python-yaml": "6.0.3-2",
"qemu-guest-agent": "11.1.0-1",
"qoi": "r350.4461cc3-1",
"qt6-base": "6.11.2-2",
"qt6-declarative": "6.11.2-1",
Expand Down Expand Up @@ -595,6 +597,6 @@
"zram-generator": "1.2.1-1",
"zstd": "1.5.7-3"
},
"requestedFileSha256": "c63acbf1a06b04b494bd65b38f1ee10847786fcbc3ff5cab4e8e0039c0582f30",
"requestedFileSha256": "8230ecd7405f07bf2c8e09246f426cc3397c2a05145197a03ffccfd3f4c62605",
"schemaVersion": 1
}
1 change: 1 addition & 0 deletions guest/packages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ polkit
python
qt6-imageformats
qt6-wayland
qemu-guest-agent
quickshell
ripgrep
sddm
Expand Down
4 changes: 4 additions & 0 deletions guest/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@
"image/png"
]
},
"guestAgent": {
"device": "virtserialport",
"port": "org.qemu.guest_agent.0"
},
"sharedFolder": {
"device": "virtio-9p-pci",
"fsdriver": "local",
Expand Down
39 changes: 39 additions & 0 deletions macos/Tests/qemu-guest-agent.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

set -euo pipefail

test_dir=$(cd "$(dirname "$0")" && pwd -P)
repo_dir=$(cd "$test_dir/../.." && pwd -P)

python3 - "$repo_dir" <<'PY'
import json
from pathlib import Path
import sys

repo = Path(sys.argv[1])
launcher = (repo / "macos/run-qemu-gpu.sh").read_text()
packages = (repo / "guest/packages.txt").read_text().splitlines()
package_lock = json.loads((repo / "guest/packages.lock.json").read_text())
spec = json.loads((repo / "guest/spec.json").read_text())

assert packages.count("qemu-guest-agent") == 1, "qemu-guest-agent must be a direct guest package"
assert "qemu-guest-agent" in package_lock["packages"], "qemu-guest-agent must be pinned in the package lock"
assert spec["runtime"]["guestAgent"] == {
"device": "virtserialport",
"port": "org.qemu.guest_agent.0",
}
assert 'case ${OMARCHY_QEMU_GUEST_AGENT:-0} in' in launcher
assert '*) fail "OMARCHY_QEMU_GUEST_AGENT must be 0 or 1" ;;' in launcher
assert launcher.count("socket,id=omarchy-guest-agent,path=$guest_agent_socket,server=on,wait=off") == 1
assert launcher.count(
"virtserialport,bus=omarchy-serial.0,nr=3,chardev=omarchy-guest-agent,name=org.qemu.guest_agent.0"
) == 1
assert "if ((guest_agent_enabled)); then\n qemu_args+=(" in launcher
assert "guest agent: disabled" in launcher
assert "guest agent socket: %q" in launcher
assert "[[ -S $guest_agent_socket ]] || fail \"QEMU did not create its private guest-agent socket\"" in launcher
assert 'guest_agent_socket="/tmp/${work_dir##*/}/qga.sock"' in launcher
assert "umask 077\nwork_dir=$(mktemp -d '/private/tmp/omarchy-qemu-gpu.XXXXXX')" in launcher
PY

printf 'qemu-guest-agent.test: PASS\n'
33 changes: 32 additions & 1 deletion macos/run-qemu-gpu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ runtime = exact_keys(
"devices",
"disk",
"graphics",
"guestAgent",
"hypervisor",
"initramfs",
"initramfsSource",
Expand Down Expand Up @@ -383,6 +384,10 @@ clipboard = {
"port": "dev.tryomarchy.clipboard",
"formats": ["text/plain;charset=utf-8", "image/png"],
}
guest_agent = {
"device": "virtserialport",
"port": "org.qemu.guest_agent.0",
}
shared_folder = {
"device": "virtio-9p-pci",
"fsdriver": "local",
Expand Down Expand Up @@ -432,6 +437,7 @@ if (
or runtime.get("audio") != audio
or runtime.get("storage") != storage
or runtime.get("clipboard") != clipboard
or runtime.get("guestAgent") != guest_agent
or runtime.get("sharedFolder") != shared_folder
or runtime.get("devices") != expected_devices
or runtime.get("minimumMemoryMiB") != 2048
Expand Down Expand Up @@ -750,6 +756,14 @@ if [[ -n $shared_folder ]]; then
shared_folder_kernel_argument=" omarchy.shared_folder_name=$shared_folder_name_encoded"
fi

# QGA is a root-capable guest command channel. Keep it disabled unless an
# explicit developer launch requests the private per-boot Unix socket.
case ${OMARCHY_QEMU_GUEST_AGENT:-0} in
1) guest_agent_enabled=1 ;;
0) guest_agent_enabled=0 ;;
*) fail "OMARCHY_QEMU_GUEST_AGENT must be 0 or 1" ;;
esac
Comment on lines +761 to +765

work_dir=""
owner_marker=""
owner_token=""
Expand Down Expand Up @@ -880,6 +894,7 @@ chmod 600 "$owner_marker"
qmp_socket="/tmp/${work_dir##*/}/qmp.sock"
audio_bridge_socket="/tmp/${work_dir##*/}/audio.sock"
clipboard_bridge_socket="/tmp/${work_dir##*/}/clipboard.sock"
guest_agent_socket="/tmp/${work_dir##*/}/qga.sock"
audio_route_dir="/tmp/${work_dir##*/}/audio-routes"
mkdir -m 700 "$work_dir/audio-routes"

Expand Down Expand Up @@ -963,6 +978,13 @@ qemu_args=(
-device 'virtserialport,bus=omarchy-serial.0,nr=2,chardev=omarchy-clipboard-bridge,name=dev.tryomarchy.clipboard'
)

if ((guest_agent_enabled)); then
qemu_args+=(
-chardev "socket,id=omarchy-guest-agent,path=$guest_agent_socket,server=on,wait=off"
-device 'virtserialport,bus=omarchy-serial.0,nr=3,chardev=omarchy-guest-agent,name=org.qemu.guest_agent.0'
)
fi

if [[ -n $shared_folder ]]; then
# security_model=none performs every host operation as this Mac user and
# ignores guest chown requests, so the Mac keeps real modes and ownership.
Expand Down Expand Up @@ -998,6 +1020,11 @@ if [[ ${OMARCHY_QEMU_GPU_DRY_RUN:-0} == 1 ]]; then
else
printf '\n[qemu-gpu] shared folder: disabled' >&2
fi
if ((guest_agent_enabled)); then
printf '\n[qemu-gpu] guest agent socket: %q' "$guest_agent_socket" >&2
else
printf '\n[qemu-gpu] guest agent: disabled' >&2
fi
printf '\n' >&2
exit 0
fi
Expand All @@ -1020,13 +1047,17 @@ printf '%s\n' "$qemu_pid" >"$work_dir/.qemu.pid"
chmod 600 "$work_dir/.qemu.pid"

for ((attempt = 0; attempt < 100; attempt++)); do
[[ -S $qmp_socket && -S $audio_bridge_socket && -S $clipboard_bridge_socket ]] && break
[[ -S $qmp_socket && -S $audio_bridge_socket && -S $clipboard_bridge_socket ]] &&
{ ((!guest_agent_enabled)) || [[ -S $guest_agent_socket ]]; } && break
kill -0 "$qemu_pid" 2>/dev/null || fail "QEMU exited before creating its private QMP socket"
sleep 0.05
done
[[ -S $qmp_socket ]] || fail "QEMU did not create its private QMP socket"
[[ -S $audio_bridge_socket ]] || fail "QEMU did not create its private audio bridge socket"
[[ -S $clipboard_bridge_socket ]] || fail "QEMU did not create its private clipboard bridge socket"
if ((guest_agent_enabled)); then
[[ -S $guest_agent_socket ]] || fail "QEMU did not create its private guest-agent socket"
fi
echo "[qemu-gpu] Ready." >&2

# FD 9 deliberately remains open only in QEMU. Letting the sibling audio
Expand Down
Loading