dragonmoonlight/README.md

8.4 KiB

DragonMoonlight

A fork of moonlight-qt with native DragonRelay integration and an embedded WireGuard client.

Instead of managing a separate VPN app, DragonMoonlight authenticates against your DragonRelay server, auto-provisions a WireGuard peer, brings up the tunnel, and presents your streaming hosts — all in one UI.

Architecture

┌─────────────────────────────────┐       ┌──────────────────────────────┐
│  DragonMoonlight (this fork)    │       │  DragonRelay (homelab)       │
│                                 │       │                              │
│  RelayClient ─── HTTPS ────────►│──────►│  POST /api/vpn/peer          │
│       │                         │       │  (returns WireGuard .conf)   │
│       ▼                         │       │                              │
│  TunnelManager                  │       │  wg0 interface               │
│  ├─ boringtun (WireGuard/Rust)  │◄═════►│  10.99.0.1                   │
│  ├─ utun (macOS, no root*)      │  WG   │                              │
│  ├─ Wintun (Windows, Admin)     │       │  Hosts registered by Artemis │
│  └─ /dev/net/tun (Linux, CAP)   │       │  via /api/host/register      │
│                                 │       │                              │
│  DragonRelayView.qml            │       └──────────────────────────────┘
│  (host list, stream button)     │
└─────────────────────────────────┘

* Route setup requires one macOS admin auth dialog per session.

New files (vs upstream moonlight-qt)

app/vpn/
├── boringtun_ffi.h            C ABI for the boringtun Rust library
├── wireguardconfig.h/.cpp     WireGuard .conf parser
├── tunnelmanager.h            Cross-platform tunnel interface
├── tunnelmanager_mac.mm       macOS implementation (utun + boringtun)
├── tunnelmanager_win.cpp      Windows implementation (Wintun + boringtun)
├── tunnelmanager_linux.cpp    Linux implementation (/dev/net/tun + boringtun)
├── wintun.h                   Wintun dynamic loader (Windows)
├── relayclient.h/.cpp         HTTP client for the DragonRelay API
├── dragonrelaybackend.h/.cpp  QObject bridge: RelayClient + TunnelManager → QML
└── CMakeLists_vpn.cmake       Build system additions

app/gui/
├── DragonRelayView.qml        Connect / host-list UI page
└── DragonDisplayPicker.qml    Multi-display selection modal

app/
├── DragonMoonlight.manifest   Windows UAC manifest (requireAdministrator)
├── main.cpp                   Entry point with QML engine + dragonRelay backend
└── assets/                    WildDragon logo + qrc

scripts/
├── build-boringtun.sh         Compiles boringtun for Linux/macOS
├── build-boringtun-win.ps1    Compiles boringtun for Windows
├── build-installer-mac.sh     macOS .dmg + .pkg installer
├── build-installer-win.ps1    Windows .exe installer (Inno Setup)
└── DragonMoonlight.iss        Inno Setup script

package/
├── mac/                       macOS code-signing entitlements + DMG art
└── win/                       Windows pre-install elevation/version checks

deps/boringtun/                Created by build scripts (git-ignored)

Building

macOS

brew install qt@6 cmake rustup
rustup-init && rustup target add aarch64-apple-darwin x86_64-apple-darwin

git clone https://forge.wilddragon.net/zgaetano/dragonmoonlight && cd dragonmoonlight
git remote add upstream https://github.com/moonlight-stream/moonlight-qt.git
git fetch upstream && git merge upstream/master --allow-unrelated-histories
git submodule update --init --recursive

bash scripts/build-boringtun.sh --universal

mkdir build && cd build
cmake .. -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6)"
make -j$(sysctl -n hw.logicalcpu)

Windows

# Prerequisites: Visual Studio Build Tools 2019+, Rust, CMake, Qt 6, Inno Setup
git clone https://forge.wilddragon.net/zgaetano/dragonmoonlight
cd dragonmoonlight
git remote add upstream https://github.com/moonlight-stream/moonlight-qt.git
git fetch upstream
git merge upstream/master --allow-unrelated-histories
git submodule update --init --recursive

pwsh scripts/build-boringtun-win.ps1

cmake -B build -A x64 -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release

# Optional: build the .exe installer
pwsh scripts/build-installer-win.ps1

wintun.dll (x64) must sit next to DragonMoonlight.exe at run time. The installer script downloads it automatically; for ad-hoc builds, fetch it from https://www.wintun.net and copy it to the build output directory.

Linux

sudo apt install qt6-base-dev qt6-declarative-dev qml6-module-qtquick \
                 cmake build-essential libcurl4-openssl-dev rustup

bash scripts/build-boringtun.sh

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j

# Grant net-admin so the binary can open /dev/net/tun without being root:
sudo setcap cap_net_admin+ep ./build/dragonmoonlight

How it works (connection flow)

User clicks "Connect" in DragonRelayView
       │
       ▼
RelayClient.login(url, user, pass)
       │ POST /api/auth/login → JWT
       ▼
RelayClient.provisionVPN(deviceName)
       │ POST /api/vpn/peer → {id, conf}
       ▼
WireGuardConfig::fromConf(conf)
       │
       ▼
TunnelManager.start(cfg)        (mac/win/linux platform impl)
       ├─ new_tunnel() via boringtun FFI
       ├─ Open utun/Wintun/tun device
       ├─ UDP socket → server endpoint
       ├─ configureInterface() → routes
       └─ I/O threads (tunToUdp, udpToTun, ticker)
              │
              ▼
       WireGuard handshake completes
              │
              ▼
       udpToTunThread emits tunnelUp() on first decrypted packet
              │
              ▼
       RelayClient.fetchHosts() → GET /api/hosts
              │
              ▼
       DragonRelayView shows host list
              │
       User clicks "Stream"
              │
              ▼
       dragonRelay.streamHost(ip, "Desktop")
       (launches Moonlight streaming session via QProcess)

Privilege model

Operation macOS Windows Linux
Open TUN device none Admin (Wintun driver) CAP_NET_ADMIN
Configure address + routes one osascript dialog none (IP Helper API) CAP_NET_ADMIN
Streaming (RTSP/UDP) none none none

The macOS dialog appears once when the VPN first connects; subsequent reconnects in the same session reuse the saved original-gateway and only need the dialog again if the interface was torn down.

Production path (macOS): Replace the osascript approach with a signed, sandboxed SMJobBless privileged helper or a NetworkExtension PacketTunnelProvider entitlement (requires Apple developer approval).

Roadmap

  • DragonRelayBackend C++ ↔ QML bridge
  • DragonRelayView is the navigation entry point exposed to QML
  • Windows support (tunnelmanager_win.cpp using Wintun)
  • Linux support (tunnelmanager_linux.cpp using boringtun userspace + /dev/net/tun)
  • tunnelUp() signal from first received packet (with 1500 ms fallback)
  • Multi-monitor / display selection (Sunshine API integration via DragonDisplayPicker)
  • Replace osascript with SMJobBless privileged helper on macOS
  • Replace plain-text password storage with system keychain (QKeychain)
  • Auto-reconnect on network change

Notes for upstream maintainers

  • app/main.cpp is the entry point; it instantiates a DragonRelayBackend and registers it as the dragonRelay context property before loading main.qml.
  • All Dragon-specific files live under app/vpn/, app/gui/Dragon*, and scripts/. Upstream app/main.qml is unmodified — DragonRelayView is loaded on demand by user action in the existing PcView navigation stack.
  • The boringtun FFI header is identical to the one used by Artemis; both projects link the same boringtun build artifact and the FFI surface must stay in lock-step.