ref:main

Pairing modes: VMs as Moonlight apps in one instance, plus CLI pairing #13

open Opened by cole.christensen@gmail.com

Two ways to pair, and a way to finish pairing without a GUI.

Why

Phase 1 ships one Sunshine process per VM (#6). Each instance has its own identity, so a client pairs with every VM separately: N PINs, N web UI logins, N hosts in Moonlight. That is the right shape when two VMs must stream at once, and the wrong shape for day-to-day development, where one person streams one VM at a time and just wants to pick which.

Pairing also can’t be finished from a terminal today. GET/POST /api/pin exist, but the only client is the web UI, so a headless setup has to drive a browser.

Mode A: VMs as apps in one instance (REQ-DEP-003, the default for development)

One Sunshine, one pairing, one port set, one mDNS name. Each VM is an app in Moonlight’s list; launching it streams that VM. One VM at a time.

This fits Sunshine’s existing lifetimes: the capture thread (src/video.cpp:709) and the audio context (src/audio.cpp:277) are refcounted singletons that live for a streaming era, not the process, and they re-read config when constructed. So switching VMs between streams needs no new lifetime.

  • Per-app VM: new ctx_t field (src/process.h:57), parsed in src/process.cpp, added to apps.json and the apps web UI. Empty means the global qemu_dbus_address.
  • Resolve the app’s VM at launch before configure_display and probe_encoders (src/nvhttp.cpp:1387, :1393), which both run before proc::proc.execute (:1414).
  • platf::init() latches the QEMU source only when verify_qemu() can reach the configured address (src/platform/linux/misc.cpp:1405). With no VM booted at startup, capture stays disabled for the process lifetime. Select the backend on capture = qemu and verify per launch instead.
  • shared_session is a single-slot cache keyed on one address (qemu/session.cpp:1600). Make it a map so switching VMs doesn’t reconnect every time.
  • qemu::input_t binds the address once at startup (virtualhid_input.cpp:1046). The capture binding already redirects input to the streaming VM; fix the pre-stream fallback.
  • Decide what display_names() reports: this VM’s consoles, or every configured VM. Keep the switch_display hotkey (src/video.cpp:1580) from jumping between VMs mid-stream.
  • Docs and an example apps.json.

Not in scope: two VMs streaming at once from one process. nvhttp.cpp:1368 allows one running app per host, and one display object is shared by every session in an era; changing either is a deep refactor of upstream. Concurrency stays with mode B.

Mode B: one instance per VM (REQ-DEP-001, unchanged)

Keeps isolation and concurrent streams. Stays the default for the systemd deployment.

  • Document when to choose which, including that mode A trusts one pairing for every VM it serves.

CLI pairing (REQ-DEP-004)

  • sunshine-qemu-pair: list pending requests, approve one with its PIN, list paired clients, unpair. Talks to an instance’s API over HTTPS with the web UI credentials. CSRF is skipped for non-browser clients (src/confighttp.cpp:790), so no browser is needed.
  • Resolve the instance from a VM name (its config’s port) or an explicit port.
  • Packaging test with a real instance.

Done when

  • A client pairs once with a mode A instance, sees each VM as an app, and streams two different VMs one after the other.
  • sunshine-qemu-pair completes a pairing end to end with no browser.
  • Mode B still passes its existing tests.