| app | ||
| package/mac | ||
| scripts | ||
| README.md | ||
🐉 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 │ │
│ │ │ mDNS → Apollo/Artemis hosts │
│ 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)
├── relayclient.h/.cpp HTTP client for the DragonRelay API
└── CMakeLists_vpn.cmake Build system additions
app/gui/
└── DragonRelayView.qml Connect / host-list UI page
scripts/
└── build-boringtun.sh Compiles boringtun as a static library
deps/boringtun/ Created by build-boringtun.sh (git-ignored)
Building (macOS)
1. Prerequisites
# Xcode Command Line Tools
xcode-select --install
# Homebrew
brew install qt@6 cmake rustup
# Rust toolchain
rustup-init
rustup target add aarch64-apple-darwin x86_64-apple-darwin
2. Clone upstream moonlight-qt into this repo
This repo holds only the DragonMoonlight additions. You need to merge with upstream:
git clone https://forge.wilddragon.net/zgaetano/dragonmoonlight
cd dragonmoonlight
# Add upstream as a remote and merge
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
3. Build boringtun
bash scripts/build-boringtun.sh --universal
# Produces: deps/boringtun/libboringtun.a (universal arm64+x86_64)
4. Integrate the VPN CMake snippet
In moonlight-qt's root CMakeLists.txt, add after the main target is defined:
include(app/vpn/CMakeLists_vpn.cmake)
5. Register DragonRelayBackend with QML
In app/main.cpp (or wherever the QML engine is set up), add:
#include "dragonrelaybackend.h" // thin QObject wrapping RelayClient + TunnelManager
// Before engine.load():
DragonRelayBackend relayBackend;
engine.rootContext()->setContextProperty("dragonRelay", &relayBackend);
DragonRelayBackend is the next class to write — it wires RelayClient ↔ TunnelManager ↔ QML.
6. Add the view to the navigation stack
In app/gui/main.qml or the computer browser stack, push DragonRelayView alongside the normal PcView.
7. Build
mkdir build && cd build
cmake .. -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6)"
make -j$(sysctl -n hw.logicalcpu)
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)
├─ new_tunnel() via boringtun FFI
├─ openUtun() → utunN fd (no root)
├─ UDP socket → server endpoint
├─ configureInterface() → osascript admin dialog (once per session)
│ ifconfig + route add for 10.99.0.0/24
└─ I/O threads (tunToUdp, udpToTun, ticker)
│
▼
WireGuard handshake completes
│
▼
RelayClient.fetchHosts() → GET /api/hosts
│
▼
DragonRelayView shows host list
│
User clicks "Stream"
│
▼
dragonRelay.streamHost(ip, "Desktop")
(launches Moonlight streaming session)
Privilege model
| Operation | Root / Admin? |
|---|---|
| Open utun device | No |
ifconfig utunN <ip> up |
Yes — one-time macOS auth dialog |
route add -net 10.99.0.0/24 -interface utunN |
Yes — same dialog, batched |
| Streaming (RTSP/UDP to Apollo host) | No |
The admin 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: Replace the osascript approach with a signed, sandboxed SMJobBless privileged helper or a NetworkExtension PacketTunnelProvider entitlement (requires Apple developer approval).
DragonRelayBackend (next step)
The QML view is wired to a dragonRelay context property. The next file to write is app/vpn/dragonrelaybackend.h/.cpp — a QObject that:
- Owns
RelayClientandTunnelManager - Exposes
Q_PROPERTYvalues (status,statusText,hosts) to QML - Has
Q_INVOKABLEmethods:connectRelay(),disconnectRelay(),streamHost() - Translates
RelayClientandTunnelManagersignals into QML-visible state changes
Roadmap
DragonRelayBackendC++ ↔ QML bridge- Integrate
DragonRelayViewinto upstreamPcView.qmlnavigation - Windows support (
tunnelmanager_win.cppusing Wintun) - Linux support (
tunnelmanager_linux.cppusing kernel WireGuard) - Replace
osascriptwith SMJobBless privileged helper on macOS - Replace plain-text password storage with system keychain (QKeychain)
connected()signal from first received packet rather than a fixed timer- Multi-monitor / display selection (Sunshine API integration)