Configuration¶
Ryll can be configured via command-line arguments and .vv configuration files.
Command Line Options¶
Connection Source (one required)¶
| Option | Description |
|---|---|
--url <URL> |
Fetch .vv configuration file from HTTP URL |
--file <PATH> |
Load .vv configuration from local file |
--direct <CONN> |
Direct connection: HOST:PORT or HOST:PORT:TLS_PORT |
--password-file <PATH> |
With --direct, read the SPICE password from a file. One trailing newline is stripped, so a server's ticket file (andris's ~/.config/andris/ticket, for example) works as-is. An empty or unreadable file fails the launch. A .vv file carries its own password, so this is refused with --url and --file |
Operating Modes¶
| Option | Default | Description |
|---|---|---|
--headless |
false | Run without GUI (for automated testing) |
--cadence |
false | Send automatic keystroke every 2 seconds |
USB Device Redirection¶
| Option | Default | Description |
|---|---|---|
--usb-disk <PATH> |
none | Present a RAW disk image as a USB mass storage device (repeatable) |
--usb-disk-ro <PATH> |
none | Same as --usb-disk but read-only (repeatable) |
Folder Sharing¶
| Option | Default | Description |
|---|---|---|
--share-dir <PATH> |
none | Share a local directory with the guest via WebDAV |
--share-dir-ro |
false | Make the shared directory read-only |
Guest requirements: The guest VM needs spice-webdavd installed (from
the phodav project) and davfs2 for mounting. The QEMU VM must be configured
with a spiceport chardev named org.spice-space.webdav.0 — see the QEMU
configuration section below.
Bug Reports and Auto-snapshot Mode¶
| Option | Default | Description |
|---|---|---|
--bug-report-dir <DIR> |
none | Output directory for manual and auto-snapshot bug reports (falls back to <capture>/bug-reports/ if --capture is set, then current directory) |
--auto-snapshot-interval <SECONDS> |
disabled | Enable flight-data-recorder mode: fire a complete bug-report zip every N seconds into <bug-report-dir>/auto-snapshots/. Minimum recommended interval is 10 seconds; 0 is rejected at startup |
--auto-snapshot-cap <N> |
20 | Maximum number of auto-snapshot zips to keep on disk; oldest are pruned when capacity is exceeded. A 512 MiB budget for the same directory is enforced alongside it, so the cap you set is an upper bound rather than a promise — a busy session whose zips approach the 50 MiB traffic-ring budget will keep fewer than N |
--image-cache-cap-mib <N> |
256 | Cap the SPICE display image cache at N MiB. The cache holds decoded RGBA frames flagged with CACHE_ME by the server; an LRU evicts oldest entries when the cap is exceeded. Default is enough for typical desktop sessions, but increase it for heavy CACHE_ME workloads (sustained video) or lower it on small-RAM hosts. Must be at least 1 |
--glz-dictionary-cap-mib <N> |
256 | Cap the shared SPICE GLZ decompression dictionary at N MiB. The dictionary holds decoded RGBA entries that the server attached IMAGE_FLAGS_CACHE_ME to on Glz / ZlibGlz payloads so cross-frame back-references resolve; without a cap, full-frame ZlibGlzRgb workloads (observed in sessions 003a and 004d-g) grew the dictionary at roughly 30 MiB/s and drove a multi-GiB RSS runaway before the cap existed. An LRU evicts oldest entries when the cap is exceeded. Same trade-off shape as --image-cache-cap-mib: lower it on small-RAM hosts, raise it for sustained GLZ-heavy workloads to keep more cross-frame references hot. Must be at least 1 |
--preferred-compression <MODE> |
auto-glz |
Image compression to ask spice-server for in the display channel's PREFERRED_COMPRESSION message. One of off, auto-glz, auto-lz, quic, glz, lz, lz4. Exists for diagnostics: spice-server only sends LZ4 images to a client that asks for exactly lz4, so use that to capture them. auto-glz is the default for a measured reason: asking for auto-lz made the server stop using GLZ entirely (GLZ dictionary entries 23 to 0, bytes received up 25% in session 006), and for UI-heavy workloads the shared GLZ dictionary is the win while auto-glz still lets the server choose QUIC for photographic content |
--pedantic |
false | Write a bug-report zip to ./ryll-pedantic-reports/ (or --pedantic-dir) the first time each distinct protocol gap is detected |
--pedantic-dir <DIR> |
none | Output directory for pedantic bug reports |
Web Mode¶
--web runs ryll as a SPICE-to-browser transcoder instead of opening a GUI
window. The operator guide, including reverse-proxy and TLS setup, is
web-frontend.md; this table is the flag reference.
| Option | Default | Description |
|---|---|---|
--web |
false | Run as a SPICE → browser transcoder. Serves a browser shell that consumes the SPICE display via WebRTC, and prints a URL carrying a per-launch random token. Mutually exclusive with --headless and the GUI default |
--web-host <ADDR> |
127.0.0.1 |
Bind address for the HTTP/HTTPS signalling listener only. Use 0.0.0.0 for LAN access. Has no effect on which addresses WebRTC media binds or advertises — that is --web-media-addr |
--web-port <PORT> |
0 (ephemeral) | TCP port for the signalling listener |
--web-tls-cert <PATH> |
none | PEM-encoded TLS certificate chain. Serving over HTTPS requires --web-tls-key as well |
--web-tls-key <PATH> |
none | PEM-encoded TLS private key. Required with --web-tls-cert |
--web-media-addr <ADDR\|IFACE> |
every non-loopback interface address | Local address, or interface name, to bind the WebRTC media (UDP) sockets to. Repeatable. Naming addresses explicitly also overrides the default exclusion of loopback, which is how a loopback-only host is served (--web-media-addr 127.0.0.1). The unspecified addresses (0.0.0.0, ::) and zoneless fe80::/10 are refused at startup: neither can become an ICE candidate a browser will use |
--web-media-port <PORT> |
0 (ephemeral) | UDP port for the media sockets. Pin it so a firewall rule can name one port instead of the OS's whole ephemeral range. Applies to every bound address; a port already in use fails loudly rather than falling back to an ephemeral one, since a silent fallback would no longer match the firewall rule |
--web-ice-server <URL> |
none | STUN or TURN server URL (stun:host:port, turn:host:port). Repeatable. Empty by default: ryll assumes browser and host share a LAN, so ICE host candidates are usually enough. The scheme must be stun:, stuns:, turn: or turns: and is checked at startup, so a URL with the scheme left off fails the launch rather than producing an ICE server that silently does nothing |
RDP Mode¶
--rdp runs ryll as a SPICE-to-RDP server instead of opening a GUI window, so
a stock RDP client can drive the guest. It is a reachability fallback for
users with no SPICE client, and the RDP client cannot use SPICE features such
as USB redirection and folder sharing. The binary must be built with the
rdp feature, which release builds leave out; one built without it refuses
--rdp with a clear message. The operator guide, including what works, how
clients are served, certificates and running it as a service, is
rdp-frontend.md; this table is the flag reference.
| Option | Default | Description |
|---|---|---|
--rdp |
false | Run as a SPICE → RDP server. Prints the address, username, password and security mode at launch, and the certificate fingerprint when the certificate is generated. Mutually exclusive with --web and --headless; --control-socket works with it |
--rdp-host <ADDR> |
127.0.0.1 |
Bind address for the RDP listener. Use 0.0.0.0 for LAN access |
--rdp-port <PORT> |
3389, or an ephemeral port if 3389 is taken | TCP port to listen on. The port actually bound is printed at launch. A port given explicitly is used or the launch fails; 0 asks for an ephemeral port |
--rdp-user <NAME> |
ryll |
Username RDP clients log in with |
--rdp-password <PASSWORD> |
generated | Password RDP clients log in with. When absent, a random password is generated and printed at each launch. A password on the command line is visible to other users of the host in the process list, so prefer the generated one on a shared machine |
--rdp-security <MODE> |
tls |
How clients authenticate: tls (TLS with the credentials checked by ryll; every client speaks it) or nla (CredSSP, which refuses clients that cannot do it) |
--rdp-tls-cert <PATH> |
self-signed, generated at launch | PEM-encoded TLS certificate chain. Requires --rdp-tls-key as well |
--rdp-tls-key <PATH> |
none | PEM-encoded TLS private key. Required with --rdp-tls-cert |
Capture and Debugging¶
| Option | Default | Description |
|---|---|---|
--capture <DIR> |
none | Write pcap + video capture to directory |
-v, --verbose |
false | Enable debug-level logging |
--latency-file <PATH> |
none | Write latency measurements to CSV file |
--experimental-h264 |
false | Experimental: prefer H.264 video streams, asking for H.264 ahead of MJPEG in the display channel's PREFERRED_VIDEO_CODEC_TYPE. By default ryll asks for MJPEG first, which keeps spice-server on MJPEG, because H.264 from spice-server does not decode yet (#398): with this flag its video regions are likely to go black or stale. For work on that bug; see H.264 from spice-server. Cannot be combined with --no-h264 |
--no-h264 |
false | Do not offer H.264 video streams: leave out the display channel's CODEC_H264 capability and ask only for MJPEG. A server that encodes only H.264, such as andris, then sends no video streams. An escape hatch for a server whose H.264 does not decode, such as a guest's spice-streaming-agent; see H.264 from spice-server. Cannot be combined with --experimental-h264 |
Examples¶
# Connect using a .vv file from a URL
ryll --url https://cloud.example.com/console/vm-123.vv
# Connect using a local .vv file
ryll --file ~/downloads/myvm.vv
# Direct connection to a SPICE server (insecure)
ryll --direct 192.168.1.100:5900
# Direct connection with TLS
ryll --direct 192.168.1.100:5900:5901
# Direct connection to a server that wants a password
ryll --direct 192.168.1.100:5900 --password-file ~/.config/andris/ticket
# Headless mode for automated testing
ryll --file test.vv --headless
# Performance testing with latency tracking
ryll --file test.vv --cadence --latency-file latency.csv
# Capture protocol traffic and display video
ryll --file test.vv --capture /tmp/capture
# Verbose output for debugging
ryll --file test.vv -v
# Attach a RAW disk image as a USB flash drive
ryll --file test.vv --usb-disk /path/to/image.raw
# Attach a read-only USB disk
ryll --file test.vv --usb-disk-ro /path/to/image.raw
# Headless mode with USB disk
ryll --file test.vv --headless --usb-disk /tmp/test.raw
# Share a local directory with the guest
ryll --file test.vv --share-dir /home/user/documents
# Share a directory read-only
ryll --file test.vv --share-dir /home/user/documents --share-dir-ro
# Headless mode with folder sharing
ryll --file test.vv --headless --share-dir /tmp/test-share
# Enable auto-snapshot mode: fire a bug report every 30 seconds, keep last 20
# (or fewer, if 20 of them would exceed the 512 MiB directory budget)
ryll --file test.vv --auto-snapshot-interval 30
# Custom output directory and rolling cap
ryll --file test.vv --auto-snapshot-interval 30 \
--auto-snapshot-cap 10 --bug-report-dir /tmp/session-debug
# Pedantic mode: report on protocol gaps
ryll --file test.vv --pedantic
.vv File Format¶
The .vv (virt-viewer) file is an INI-format configuration file. Ryll supports
the standard virt-viewer format used by remote-viewer and other SPICE clients.
Basic Format¶
Supported Fields¶
| Field | Required | Description |
|---|---|---|
host |
Yes | SPICE server hostname or IP address |
port |
No* | SPICE insecure port (usually 5900+) |
tls-port |
No* | SPICE TLS port for secure connections |
password |
No | SPICE password for authentication |
ca |
No | Inline PEM CA certificate for TLS verification. When present it is the only trust anchor for the connection -- the public CA roots are not trusted as well |
host-subject |
No | Server certificate subject; enforced -- the connection fails if the server's certificate subject does not match, and a malformed value is rejected at startup. Without it, a ca connection whose certificate name does not match is trusted on the CA alone, and ryll warns (see troubleshooting) |
proxy |
No | HTTP CONNECT proxy to tunnel the connection through (e.g. Proxmox VE's spiceproxy); see HTTP CONNECT proxy below |
Ticket lifecycle keys¶
ryll also reads two ticket-related keys with ryll-specific behaviour:
delete-this-file=1— the standard "remove this file after reading" hint. As remote-viewer does, ryll deletes a--file.vvas soon as it has parsed it, before connecting, so the ticket it carries is not left behind in the downloads directory. A file that fails to parse is kept, and a failed removal is logged as a warning without stopping the connection. ryll additionally treats this as a signal that the SPICE ticket is single-use: any reconnect attempt would be rejected by the server, so auto-reconnect is suppressed and a "single-use ticket" modal is shown instead of the normal retry sequence.ticket-valid-until=<unix-ts>— a ryll extension key (no equivalent in remote-viewer). When set, ryll surfaces a T-30s warning notification, suppresses auto-reconnect once the deadline has passed, and shows a "ticket expired" modal with the expiry time.
Both keys degrade gracefully — absent values mean the previous
ryll behaviour. Producer-side documentation lives in the
companion doc
console-vv-extensions.md
in the kerbside-wt-docs repository, which is the canonical
reference for SPICE deployment authors (Kerbside, oVirt,
custom gateways) who want their .vv output to drive ryll's
reconnect UX correctly.
HTTP CONNECT proxy¶
proxy=<uri>— tunnels the SPICE connection through an HTTP CONNECT proxy, the way Proxmox VE'sspiceproxyis reached. Accepts an optionalhttp://scheme (the default when omitted), a barehost,host:port,http://host:port, or a bracketed IPv6 host ([::1]:3128); the port defaults to 3128 when none is given. Trailing slashes are stripped.
Two forms are parsed and refused, each with its own error naming the
unsupported feature rather than a generic parse failure: an
https:// scheme (TLS to the proxy itself is not supported; use
http://), and user:pass@ credentials in the URI (proxy
authentication is not supported). A malformed or refused proxy=
value fails the .vv load, naming the proxy key, before any
connection is attempted. The error quotes the value unless it
contains an @, whatever the scheme, so credentials never reach
stderr, logs or a bug report.
A tunnelled connection is TLS-only and must be pinned: a .vv that
sets proxy must also set both tls-port and host-subject. ryll
refuses to dial -- before opening a socket -- a .vv that sets
proxy without one of the two. This is not optional hardening: under
a tunnel, host is not a name TLS can verify at all (see the Proxmox
example below), so the certificate-subject pin is the only identity
check the connection has.
The CONNECT exchange with the proxy must finish within 10 seconds. A proxy that accepts the TCP connection and then never answers fails the dial with "HTTP proxy did not complete the CONNECT exchange" rather than holding it open.
When a connection is tunnelled, ryll's info log, capture
metadata.json, and any bug report show the proxy's address with
the target redacted (<proxy host>:<proxy port> (tunnelled, target
redacted)) rather than the raw host, because under Proxmox host
carries a signed ticket that should not end up in a log line or in
an artefact attached to a public issue.
Example .vv Files¶
Minimal (insecure connection):
With password:
TLS connection with inline CA (as generated by Shaken Fist):
[virt-viewer]
type=spice
host=spice.example.com
tls-port=5901
ca=-----BEGIN CERTIFICATE-----\nMIIE...(base64)...\n-----END CERTIFICATE-----\n
Note: the ca= field contains the PEM certificate inline with \n
escape sequences for newlines, not a file path. It replaces the
public CA roots for that connection rather than adding to them, so
omit it for a server whose certificate comes from a public CA.
Either port or tls-port (or both) must be specified.
Full configuration:
[virt-viewer]
type=spice
host=spice.example.com
port=5900
tls-port=5901
password=mysecretpassword
ca=-----BEGIN CERTIFICATE-----\nMIIE...\n-----END CERTIFICATE-----\n
host-subject=CN=spice.example.com
Note: host-subject must match the server certificate's subject
exactly, including attribute count, order, and type -- not just the
CN value.
Proxmox VE (tunnelled through spiceproxy):
[virt-viewer]
type=spice
host=pvespiceproxy:1a2b3c4d:100:pve1.example.com:61000::REDACTED-TICKET
proxy=http://pve1.example.com:3128
tls-port=61000
host-subject=OU=PVE Cluster Node,O=Proxmox Virtual Environment,CN=pve1.example.com
ca=-----BEGIN CERTIFICATE-----\nMIIE...\n-----END CERTIFICATE-----\n
password=REDACTED-TICKET
delete-this-file=1
Note: host here is not a hostname ryll resolves; it is an opaque,
signed pseudo-hostname that Proxmox's spiceproxy interprets once the
CONNECT tunnel reaches it, and the password above carries the same
kind of ticket. Both are placeholders: a real Proxmox ticket is valid
for only about 30 seconds, so a Proxmox .vv must be downloaded and
opened promptly, before it expires. delete-this-file=1 is why a
dropped session does not retry against a now-dead ticket -- it
suppresses ryll's auto-reconnect for this single-use ticket, per the
Ticket lifecycle keys above.
This example is exercised, not just documented: the Proxmox
lane mints a .vv in this shape against a
real Proxmox VE node on every pull request from this repository that
touches the CONNECT tunnelling code, and asserts that ryll connects
through it.
Keyboard Shortcuts¶
These shortcuts are available during a GUI session. They are consumed by ryll and not forwarded to the guest VM.
| Shortcut | Action |
|---|---|
| F8 | Save the current display as PNG(s) via a native file dialog |
| F11 | Toggle the live traffic viewer side panel |
| F12 | Open / close the bug report dialog |
| Escape | Close the bug report dialog, or skip region selection |
On macOS, Cmd chords belong to the host: a key pressed while Cmd is held (Cmd+Q, Cmd+W, Cmd+C and so on) is not forwarded to the guest, and neither is its release. ryll does not map Cmd to the guest's Super / Windows key, so there is currently no way to send a Super chord to the guest from the macOS GUI. A key that was already held when Cmd went down is still released in the guest normally.
On Linux and Windows, Ctrl+C, Ctrl+X and Ctrl+V go to the guest like any other Ctrl chord, so Ctrl+C interrupts a program in a guest terminal. They do not copy, cut or paste on the host, and Ctrl+V does not type the host clipboard (Ctrl+Alt+V does that, when paste-as-keystrokes is enabled, and is never forwarded to the guest, even when the host clipboard is empty). The GUI toolkit reports these chords by what they mean rather than by key, so ryll sends the key in its US-QWERTY position: on a layout that moves C, X or V (Dvorak, for example) the guest gets the key at that position instead. On Windows, Shift+Delete and Shift+Insert reach the guest as themselves, but Ctrl+Insert reaches it as Ctrl+C and Ctrl+Shift+Delete as Ctrl+Shift+X, because ryll cannot tell them apart from those.
Environment Variables¶
Currently, ryll does not use environment variables for configuration. All settings are provided via command-line arguments or .vv files.
Logging¶
Ryll uses the tracing crate for logging. By default, INFO-level messages
are shown. Use -v or --verbose for DEBUG-level output.
Log output goes to stderr, so you can redirect stdout for data output:
Latency File Format¶
When --latency-file is specified, ryll writes latency measurements in CSV
format:
- timestamp: Unix timestamp of the measurement
- latency_ms: Time from keystroke to display update in milliseconds
This is primarily useful in cadence mode, where keystrokes are generated at known intervals.
USB Device Redirection¶
Ryll can present a RAW disk image as a USB mass storage device to the remote VM. The guest OS sees a standard USB flash drive and can partition, format, mount, read, and write it.
Creating a Test Image¶
# Create a 64MB empty image
dd if=/dev/zero of=test.raw bs=1M count=64
# Optionally format it with a filesystem
mkfs.ext4 test.raw
QEMU Requirements¶
The SPICE server (QEMU) must have USB redirection enabled:
-device qemu-xhci,id=xhci
-chardev spicevmc,id=usbredir1,name=usbredir
-device usb-redir,chardev=usbredir1,id=redir1
Use make test-qemu-usb to start a pre-configured QEMU instance.
Usage¶
# GUI mode
ryll --direct localhost:5900 --usb-disk test.raw
# Headless mode
ryll --direct localhost:5900 --headless --usb-disk test.raw
# Read-only (guest cannot write to the image)
ryll --direct localhost:5900 --usb-disk-ro test.raw
Notes¶
- Only the first
--usb-disk/--usb-disk-rois connected (SPICE supports one device per usbredir channel). - The image file must be at least 512 bytes.
- If the file size is not a multiple of 512, trailing bytes are inaccessible (a warning is logged).
- The device auto-connects when the usbredir channel's hello exchange completes.
- Use
--capture <DIR>to capture usbredir protocol traffic tousbredir.pcapfor debugging.
USB Panel (GUI Mode)¶
In GUI mode, click the USB button in the status bar to open the USB device management panel. The panel shows:
- Channel status — whether a usbredir channel is available from the SPICE server.
- Connected device — the currently redirected device with elapsed connection time.
- Device list — all available USB devices (physical and virtual).
Physical devices are enumerated from the host via nusb. Virtual devices
come from
--usb-diskflags and runtime additions. Each device has a Connect or Disconnect button. - Add Disk... — opens a native file picker to add a RAW disk image as
a virtual USB device for the current session. A "Read-only" checkbox
controls write access. The file is validated (must be a regular file,
= 512 bytes).
- Refresh — re-enumerates available devices. Useful if USB devices were plugged in or removed after the panel was opened.
- Error display — connection failures appear in red with a Dismiss button and a "Report this as a bug" button that opens the bug report dialog pre-populated with the USB error and usbredir channel context.
The panel can be open simultaneously with the Traffic viewer panel.
Devices added via the panel persist for the session but are not saved
across restarts — use --usb-disk for persistent configuration.
Physical USB device passthrough requires the host to have accessible USB devices (appropriate permissions, not claimed by a kernel driver). The panel enumerates whatever nusb can see — if the list is empty, check host USB permissions.
Folder Sharing (WebDAV)¶
GUI Panel¶
Click "Folders" in the status bar to open the Folders panel. It shows:
- Channel status — whether the SPICE WebDAV channel is connected.
- Active share — the shared directory path, read-only status, and elapsed time since sharing started.
- Share Directory... — opens a native directory picker to select a folder to share. A "Read-only" checkbox controls write access.
- Stop Sharing — stops the current share and disconnects all clients.
- Error display — errors appear in red with a Dismiss button and auto-clear after 10 seconds.
QEMU Configuration¶
The QEMU VM must have a spiceport chardev for folder sharing:
-device virtio-serial-pci,id=virtio-serial0
-chardev spiceport,name=org.spice-space.webdav.0,id=webdav0
-device virtserialport,chardev=webdav0,name=org.spice-space.webdav.0
Or in libvirt domain XML:
<channel type='spiceport'>
<source channel='org.spice-space.webdav.0'/>
<target type='virtio' name='org.spice-space.webdav.0'/>
</channel>
Use make test-qemu-webdav to start a pre-configured QEMU instance.
Guest Requirements¶
The guest needs:
- spice-webdavd — daemon that bridges the SPICE channel to a local
WebDAV endpoint (from the phodav project, apt install spice-webdavd)
- davfs2 — to mount the WebDAV share as a filesystem
(apt install davfs2)