diff --git a/README.md b/README.md index 5205711..38a8f16 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,189 @@ -# dragonmoonlight +# 🐉 DragonMoonlight -DragonMoonlight — a fork of moonlight-qt with embedded WireGuard and native DragonRelay integration \ No newline at end of file +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)