Skip to content
Merged
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
34 changes: 34 additions & 0 deletions GuestTools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SimpleVM Guest Tools for Linux

This bundle implements protocol version 2 with Python 3 standard-library code.
It targets modern systemd Debian/Ubuntu and Arch/Omarchy guests.

## Architecture

`simplevm-guest-tools.service` is the root agent. It concurrently retries the
QEMU virtio-serial port `/dev/virtio-ports/com.simplevm.agent.0` and an
AF_VSOCK listener on port 1021 for Apple Virtualization. Failure of either
transport does not stop the other. A user systemd service handles only desktop
clipboard and narrowly gated Hyprland display resizing.

The processes communicate over `/run/simplevm-guest-tools/session.sock`. The
socket is group restricted; Linux `SO_PEERCRED` checks require an unprivileged
user on the session side and UID 0 on the daemon side.

## Install

Run `./install.sh`. It requests sudo itself, creates the `simplevm-agent`
account/group, installs and enables both units, and starts the system service.
Use `--with-wayland-clipboard` to install `wl-clipboard`, or
`--with-x11-agent` to install `spice-vdagent`.

Wayland clipboard support uses `wl-copy` and `wl-paste`. X11/GNOME does not
advertise SimpleVM clipboard support; a separately configured spice channel
may use spice-vdagent. Vanilla spice-vdagent does not solve Hyprland Wayland
clipboard or display resizing. Hyprland resize is advertised only when a live
Hyprland session and `hyprctl` are available.

Run `./uninstall.sh` to remove the code and units. The shared mount directory
is intentionally preserved. Run `./self-test.sh` for safe local tests.
For additional desktop users, add each user to `simplevm-agent` and have that
user sign out and in before starting the user unit.
18 changes: 18 additions & 0 deletions GuestTools/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Security notes

The host API is an exact allowlist. It has no command execution request and
accepts no host-provided executable, shell text, mount source, mount option, or
path. Privileged subprocesses use fixed argument arrays with `shell=False`.
The only mount is virtiofs tag `share` at `/mnt/simplevm-share`; power actions
are `systemctl poweroff` and `systemctl reboot`.

Frames are UTF-8 JSON prefixed by a four-byte big-endian length. The 2 MiB
frame limit is checked before allocation. Clipboard UTF-8 is limited to exactly
1 MiB. Unknown, malformed, and oversized host frames close the connection.
Display dimensions are integers in 640..16384 by 480..16384.

The root daemon never reads desktop-user files. The unprivileged helper derives
session details from its environment and `/etc/os-release`. Internal IPC uses a
fixed runtime socket, restrictive filesystem permissions, and peer credentials.
The root unit intentionally avoids a private mount namespace so the fixed
shared-directory mount is visible to the rest of the guest.
1 change: 1 addition & 0 deletions GuestTools/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2.0.0
8 changes: 8 additions & 0 deletions GuestTools/bin/simplevm-guest-tools-daemon
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env python3
import sys

sys.path.insert(0, "/usr/lib/simplevm-guest-tools")

from simplevm_guest_tools.daemon import main

main()
8 changes: 8 additions & 0 deletions GuestTools/bin/simplevm-guest-tools-session
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env python3
import sys

sys.path.insert(0, "/usr/lib/simplevm-guest-tools")

from simplevm_guest_tools.user_helper import main

main()
105 changes: 105 additions & 0 deletions GuestTools/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/bin/sh
set -eu

SOURCE_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
INSTALL_USER=${SIMPLEVM_INSTALL_USER:-${SUDO_USER:-}}
INSTALL_WAYLAND=0
INSTALL_X11=0

usage() {
echo "Usage: $0 [--with-wayland-clipboard] [--with-x11-agent]"
}

for argument in "$@"; do
case "$argument" in
--with-wayland-clipboard) INSTALL_WAYLAND=1 ;;
--with-x11-agent) INSTALL_X11=1 ;;
--help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
done

if [ "$(id -u)" -ne 0 ]; then
echo "SimpleVM Guest Tools requires root installation; requesting sudo."
exec sudo env SIMPLEVM_INSTALL_USER="${USER:-}" "$0" "$@"
fi

if ! command -v systemctl >/dev/null 2>&1; then
echo "Error: systemd is required." >&2
exit 1
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "Python 3 is missing; installing it with the system package manager."
if command -v apt-get >/dev/null 2>&1; then
apt-get update
apt-get install -y python3
elif command -v pacman >/dev/null 2>&1; then
pacman -S --needed --noconfirm python
else
echo "Error: Python 3 is required and apt-get/pacman was not found." >&2
exit 1
fi
fi

install_optional_packages() {
packages=
[ "$INSTALL_WAYLAND" -eq 1 ] && packages="$packages wl-clipboard"
[ "$INSTALL_X11" -eq 1 ] && packages="$packages spice-vdagent"
[ -z "$packages" ] && return
if command -v apt-get >/dev/null 2>&1; then
apt-get update
# shellcheck disable=SC2086
apt-get install -y $packages
elif command -v pacman >/dev/null 2>&1; then
# shellcheck disable=SC2086
pacman -S --needed --noconfirm $packages
else
echo "Error: optional packages requested, but apt-get/pacman was not found." >&2
exit 1
fi
}

install_optional_packages

if ! getent group simplevm-agent >/dev/null 2>&1; then
groupadd --system simplevm-agent
fi
if ! id simplevm-agent >/dev/null 2>&1; then
useradd --system --gid simplevm-agent --home-dir /nonexistent \
--shell /usr/sbin/nologin simplevm-agent
fi
if [ -n "$INSTALL_USER" ] && [ "$INSTALL_USER" != root ] && id "$INSTALL_USER" >/dev/null 2>&1; then
usermod -a -G simplevm-agent "$INSTALL_USER"
fi

install -d -m 0755 /usr/lib/simplevm-guest-tools
rm -rf /usr/lib/simplevm-guest-tools/simplevm_guest_tools
install -d -m 0755 /usr/lib/simplevm-guest-tools/simplevm_guest_tools
for module in "$SOURCE_DIR"/src/simplevm_guest_tools/*.py; do
install -m 0644 "$module" /usr/lib/simplevm-guest-tools/simplevm_guest_tools/
done
install -m 0755 "$SOURCE_DIR/bin/simplevm-guest-tools-daemon" \
/usr/sbin/simplevm-guest-tools-daemon
install -m 0755 "$SOURCE_DIR/bin/simplevm-guest-tools-session" \
/usr/bin/simplevm-guest-tools-session
install -m 0644 "$SOURCE_DIR/systemd/simplevm-guest-tools.service" \
/etc/systemd/system/simplevm-guest-tools.service
install -m 0644 "$SOURCE_DIR/systemd/simplevm-guest-tools-session.service" \
/etc/systemd/user/simplevm-guest-tools-session.service
install -d -m 0755 /mnt/simplevm-share

systemctl daemon-reload
systemctl --global enable simplevm-guest-tools-session.service
systemctl --global is-enabled --quiet simplevm-guest-tools-session.service
systemctl enable --now simplevm-guest-tools.service
systemctl is-enabled --quiet simplevm-guest-tools.service
systemctl is-active --quiet simplevm-guest-tools.service

echo "SimpleVM Guest Tools 2.0.0 installation and system service setup completed."
echo "The user helper is globally enabled and starts with the next desktop login."
if [ -n "$INSTALL_USER" ] && [ "$INSTALL_USER" != root ]; then
echo "$INSTALL_USER must sign out and in once for simplevm-agent group access."
fi
echo "Wayland clipboard requires wl-clipboard."
echo "Vanilla spice-vdagent can help X11 guests; it does not provide"
echo "SimpleVM clipboard or resizing for a Hyprland Wayland session."
13 changes: 13 additions & 0 deletions GuestTools/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "SimpleVM Guest Tools for Linux",
"version": "2.0.0",
"protocolVersion": 2,
"python": ">=3.9",
"transports": [
"virtio-serial:com.simplevm.agent.0",
"vsock:1021"
],
"installPrefix": "/usr/lib/simplevm-guest-tools",
"systemUnit": "simplevm-guest-tools.service",
"userUnit": "simplevm-guest-tools-session.service"
}
19 changes: 19 additions & 0 deletions GuestTools/self-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh
set -eu

ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
export PYTHONPATH="$ROOT/src"

for asset in \
manifest.json VERSION README.md SECURITY.md install.sh uninstall.sh \
systemd/simplevm-guest-tools.service \
systemd/simplevm-guest-tools-session.service \
bin/simplevm-guest-tools-daemon bin/simplevm-guest-tools-session
do
test -f "$ROOT/$asset" || {
echo "Missing installer asset: $asset" >&2
exit 1
}
done

python3 -m unittest discover -s "$ROOT/tests" -p 'test_*.py' -v
4 changes: 4 additions & 0 deletions GuestTools/src/simplevm_guest_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""SimpleVM Linux guest tools."""

AGENT_VERSION = "2.0.0"
PROTOCOL_VERSION = 2
Loading
Loading