# 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 │ │ │ ├─ 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 ```bash 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 ```powershell # 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 and copy it to the build output directory. ### Linux ```bash 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 - [x] `DragonRelayBackend` C++ ↔ QML bridge - [x] `DragonRelayView` is the navigation entry point exposed to QML - [x] Windows support (`tunnelmanager_win.cpp` using Wintun) - [x] Linux support (`tunnelmanager_linux.cpp` using boringtun userspace + `/dev/net/tun`) - [x] `tunnelUp()` signal from first received packet (with 1500 ms fallback) - [x] 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.