DragonMoonlight — a fork of moonlight-qt with embedded WireGuard and native DragonRelay integration
Find a file
2026-05-06 20:16:28 -04:00
app Upload relayclient.cpp 2026-05-06 20:16:28 -04:00
package installer: fix wintun hash check, pre-install exit code, WinForms guard, cmake arch 2026-05-06 19:47:52 -04:00
scripts installer: fix wintun hash check, pre-install exit code, WinForms guard, cmake arch 2026-05-06 19:47:36 -04:00
README.md docs: DragonMoonlight setup and integration guide 2026-05-06 19:03:27 -04:00

🐉 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 RelayClientTunnelManager ↔ 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 RelayClient and TunnelManager
  • Exposes Q_PROPERTY values (status, statusText, hosts) to QML
  • Has Q_INVOKABLE methods: connectRelay(), disconnectRelay(), streamHost()
  • Translates RelayClient and TunnelManager signals into QML-visible state changes

Roadmap

  • DragonRelayBackend C++ ↔ QML bridge
  • Integrate DragonRelayView into upstream PcView.qml navigation
  • Windows support (tunnelmanager_win.cpp using Wintun)
  • Linux support (tunnelmanager_linux.cpp using kernel WireGuard)
  • Replace osascript with 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)