# 🐉 DragonMoonlight A fork of [moonlight-qt](https://github.com/moonlight-stream/moonlight-qt) with native [DragonRelay](https://forge.wilddragon.net/zgaetano/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 ```bash # 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: ```bash 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 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: ```cmake include(app/vpn/CMakeLists_vpn.cmake) ``` ### 5. Register DragonRelayBackend with QML In `app/main.cpp` (or wherever the QML engine is set up), add: ```cpp #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 ```bash 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 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)