From fa4cf9ea8d3e145d585f1c8ca5705f4615fb0769 Mon Sep 17 00:00:00 2001 From: Kiko Date: Thu, 5 Nov 2026 00:37:46 +0000 Subject: [PATCH 1/4] Add docs/remote-access.md: Tailscale and Headscale options (untested) Co-Authored-By: Claude Sonnet 5.5 --- docs/remote-access.md | 179 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 docs/remote-access.md diff --git a/docs/remote-access.md b/docs/remote-access.md new file mode 100644 index 0000000..5fe8f43 --- /dev/null +++ b/docs/remote-access.md @@ -0,0 +1,179 @@ +# Remote access: iPad to a Sunshine host on restrictive wifi + +**Status: planning document. Every step below is untested.** Nothing here has been run +against the real host, iPad, or network. Doc claims are cited; anything not found in the +cited docs is marked *unverified*. Nothing in this repo changes: this is a design note. + +## Problem + +Moonlight (this client) streams from a Sunshine host (called **Raven** here) over several +TCP and UDP ports. The target situation is an iPad on managed wifi that blocks those +ports, with no mobile data fallback. The usual escape is an overlay VPN whose traffic +looks like ordinary HTTPS. Only outbound TCP 443 is assumed to work. + +Sunshine's base port is 47989 and other ports are offset from it +([Sunshine configuration](https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2configuration.html)). +That page does not list the offsets; read the exact list from Sunshine's config UI. Pairing +uses the Sunshine web UI (default `https://:47990`, PIN page) +([Sunshine getting started](https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2getting__started.html)). +The web UI is LAN-only by default (`origin_web_ui_allowed: lan`); a tailnet address is not +the LAN, so pairing may need that setting changed or must be done on the LAN first. +*Unverified: whether tailnet source addresses count as `lan`.* + +## What the overlay must carry + +Moonlight video/audio/input are UDP. Tailscale and Headscale carry arbitrary IP inside +WireGuard, so UDP flows unchanged. When no direct path exists, the WireGuard packets +travel inside a TCP/HTTPS connection to a DERP relay. + +## Option A: Tailscale (hosted control plane, Tailscale DERP) + +### How it behaves on a 443-only network + +- Outbound requirements: TCP 443 for coordination and DERP; UDP 41641 and UDP 3478 only + improve direct connections. If only 443 is allowed, "connectivity still works" via + relays, but not as fast as peer-to-peer + ([Tailscale firewall ports](https://tailscale.com/kb/1082/firewall-ports)). +- Every connection starts relayed and then tries to upgrade to direct; DERP is the + fallback when direct fails. Relayed traffic is end-to-end WireGuard encrypted + ([connection types](https://tailscale.com/kb/1257/connection-types)). +- Tailscale warns DERP has limited quality of service and lower throughput. Its sample + `tailscale ping` output shows DERP at roughly 40 to 280 ms. Those are examples, not + predictions for this site. + +### Steps (all untested) + +1. Create or identify the tailnet and its owner (see Open Questions). +2. Install Tailscale on Raven; sign in to that tailnet. +3. Install the Tailscale iOS app on the iPad; sign in to the same tailnet. *Unverified: + whether the managed iPad's profile permits installing a VPN app or creating a VPN + configuration. This is the first likely blocker.* +4. In the Tailscale admin console or `tailscale status` on Raven, note Raven's tailnet IP + (`100.x.y.z`) or MagicDNS name. +5. On the LAN beforehand if possible, pair Moonlight with Raven normally. If pairing over + the tailnet: in Moonlight, add the host manually by tailnet IP; enter the PIN in + Sunshine's web UI. +6. Start a stream over the tailnet; check with `tailscale ping raven` (on any tailnet + device) whether the path is `direct` or `via DERP(...)`. +7. If relayed and unplayable, lower Moonlight resolution, FPS and bitrate first. + +### Latency limits when relayed + +Relayed means TCP-in-the-middle for UDP-like traffic: head-of-line blocking on loss, an +extra round trip through the nearest DERP region, and a throughput ceiling. Expect +playable desktop and slow-paced use, not guaranteed competitive-game latency. *No +measurement exists for this site; measure with `tailscale ping` and a trial stream.* + +## Option B: self-hosted Headscale on Yggdrasil behind 443 + +Headscale is an open-source reimplementation of the Tailscale control server; the +official Tailscale iOS app can point at it +([Apple clients](https://headscale.net/stable/usage/connect/apple/): account menu, +"Use custom coordination server", enter the `https://` URL). + +### Needs Eureka's approval first + +Nothing below is done until Eureka approves each item: + +- A new public hostname and DNS record for the control server. +- A public TLS certificate for it, and where TLS terminates. +- A new daemon (Headscale) on Yggdrasil and who deploys and survives it (Idunn + authority applies; this doc does not choose). +- Public exposure of 443 for this hostname, and possibly UDP 3478 (see below). +- A relay choice: reuse Tailscale's public DERP map (the iPad still reaches Tailscale's + relays on 443), or run Headscale's embedded DERP. +- Who may register nodes, and key expiry policy. + +### Minimal configuration shape (untested; read the example config for your release tag +before use, per [config docs](https://headscale.net/stable/ref/configuration/)) + +- `server_url`: the public `https://` hostname on 443. +- `listen_addr`: loopback or internal only; reverse proxy in front. +- TLS cert/key paths left empty when a proxy terminates TLS (Headscale then logs + `listening without TLS`, which is expected) + ([reverse proxy](https://headscale.net/stable/ref/integration/reverse-proxy/)). +- `trusted_proxies` set to the proxy address. +- A sqlite database path, a noise private key path, IP prefixes, and a DNS block as in the + shipped example config. +- `derp.urls`: keep Tailscale's default map for the first trial. Removing it makes the + embedded server a single point of failure + ([DERP](https://headscale.net/stable/ref/derp/)). +- Optional: `derp.server.enabled: true` plus `ipv4`/`ipv6`, `verify_clients: true`. + +### Proxy constraints from the docs + +- The proxy must support WebSockets: the control protocol upgrades via POST with + `Upgrade: tailscale-control-protocol`. Cloudflare proxies and Tunnels are unsupported. +- Port summary: tcp/443 public; tcp/80 only for built-in Let's Encrypt HTTP-01; udp/3478 + public if embedded DERP is enabled; tcp/50443 only for gRPC remote control; tcp/9090 + not public ([requirements](https://headscale.net/stable/setup/requirements/)). +- STUN for embedded DERP needs udp/3478, which an HTTPS reverse proxy cannot carry. On a + 443-only client network STUN is blocked anyway, so DERP-over-443 is the path that + matters. *Unverified: the docs do not say whether the embedded DERP's HTTPS endpoint + works behind a reverse proxy.* Simplest trial: leave Tailscale's DERP map in place. +- The reverse-proxy page is community maintained and may be outdated. + +### Exposure added + +One new public HTTPS hostname fronting a control plane; optionally a public relay and UDP +3478. Raven and the iPad gain no inbound ports; Sunshine ports stay on the tailnet. The +tailnet's ACLs should restrict the iPad to Raven's Moonlight ports only +(*unverified in this environment; ACL syntax differs between Headscale versions*). + +## Failure ladder + +Run top to bottom, stopping at the first success. Everything is untested. + +1. Home/LAN: confirm Moonlight streams to Raven with no overlay (baseline). +2. Tailscale on both devices at the LAN: confirm tailnet IP works, `tailscale ping` is + direct. +3. iPad on the restrictive wifi, Tailscale connected: does the app reach "Connected"? + If not: is VPN installation or profile blocked by management (stop; this is policy, + not network), or is 443 to the control plane filtered or TLS-intercepted? +4. `tailscale ping raven` from the iPad's side (use another tailnet device, or app + status): `direct` is best, `via DERP` means relay. +5. Relayed but stream stutters: lower bitrate, resolution, FPS; try H.264 over HEVC if the + iPad decoder is struggling (decode, not network, may be the limit). +6. Control plane reachable but no DERP: network may TLS-intercept or block Tailscale + domains. Option B with a hostname that is not a known Tailscale domain is the next step. +7. Wifi blocks everything but ordinary web browsing, and VPN apps are policy-blocked: + neither option works. Fall back to a browser-based path or a different network. + +## A or B + +| Question | A: Tailscale hosted | B: Headscale on Yggdrasil | +|---|---|---| +| Time to first test | Hours | Days (approvals, DNS, TLS, deploy) | +| New public exposure | None | Hostname, cert, daemon, maybe UDP 3478 | +| Works if network blocks Tailscale domains | No | Possibly (own hostname) | +| Works if network only does 443 | Yes, relayed | Yes, relayed, via Tailscale DERP or own | +| Control-plane ownership | Tailscale account holder | Eureka / GameCult | +| Latency when relayed | Tailscale DERP regions | Same, or own DERP region if run | +| Ongoing work | None | Upkeep, updates, backups | +| Recommended | Try first | Only if A fails at ladder step 3 or 6 | + +## Open Questions + +- Where does Raven sit on the network (same LAN as the iPad's home, behind carrier NAT, + behind a router that can forward ports, wired or wifi)? It decides whether direct + connections are plausible at all. +- Who holds the tailnet account for Option A (individual, org, shared), and who can add + devices and edit ACLs? +- Does the iPad's management policy allow a third-party VPN app? Nothing else matters if + not. +- Is any measurement available of latency from the restrictive network to the nearest + DERP region? +- If Option B is chosen: does Eureka want Headscale in the Yggdrasil deployment model, and + who owns it? + +## Sources + +- +- +- +- +- +- +- +- +- From 634c72c5fbe139fc8d0aca2836c1da23d3e7c952 Mon Sep 17 00:00:00 2001 From: Kiko Date: Thu, 5 Nov 2026 10:51:02 +0000 Subject: [PATCH 2/4] docs: remote access conditions, Wireguard-only Sunshine, jailbroken iPad Co-Authored-By: Claude Sonnet 5.5 --- docs/remote-access.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/remote-access.md b/docs/remote-access.md index 5fe8f43..723654f 100644 --- a/docs/remote-access.md +++ b/docs/remote-access.md @@ -4,6 +4,36 @@ against the real host, iPad, or network. Doc claims are cited; anything not found in the cited docs is marked *unverified*. Nothing in this repo changes: this is a design note. +## Conditions in force + +Set by Eureka (who holds the Wireguard network and Yggdrasil). They bound every option +below; an option that cannot meet them is out. + +- **Raven's Sunshine host is a yes in principle only if** it is bound to the Wireguard + interface and paired by Meta, and never exposed (no public or LAN-wide listener, no + port forward). *Untested and unverified: how to bind Sunshine to one interface. This + doc has not found a Sunshine setting for it; check the Sunshine configuration page and + the host firewall before relying on either.* +- **Minds get no access to the iPad** until Meta gives its Yggdrasil address and says yes. + Nothing here is to be run from, or opened on, Yggdrasil before that. Option B below is + a design only. +- **Pairing is Meta's act**, not a Mind's. + +## Client device + +The iPad has no App Store and runs an old jailbroken iOS (Taurine). The fork reaches it +through TrollStore, not the App Store. Consequences, none of them tested: + +- Step 3 of Option A ("install the Tailscale iOS app") and the Headscale "custom + coordination server" path both assume the App Store app. *Unverified: whether any + Tailscale or Wireguard client can be installed on this iPad, and whether it runs on that + iOS version.* This is now the first likely blocker, ahead of any management profile. +- *Unverified: whether a VPN client installed via TrollStore can create a VPN + configuration on a Taurine device.* A Wireguard client on the iPad is the natural fit + for the conditions above, and it is equally untested. +- Whether this fork builds for that iOS version is a build question for the + `gamecult/ios14-build` branch, not settled here. + ## Problem Moonlight (this client) streams from a Sunshine host (called **Raven** here) over several @@ -45,7 +75,7 @@ travel inside a TCP/HTTPS connection to a DERP relay. 1. Create or identify the tailnet and its owner (see Open Questions). 2. Install Tailscale on Raven; sign in to that tailnet. -3. Install the Tailscale iOS app on the iPad; sign in to the same tailnet. *Unverified: +3. Install a Tailscale client on the iPad (no App Store there; see Client device); sign in to the same tailnet. *Unverified: whether the managed iPad's profile permits installing a VPN app or creating a VPN configuration. This is the first likely blocker.* 4. In the Tailscale admin console or `tailscale status` on Raven, note Raven's tailnet IP @@ -159,7 +189,7 @@ Run top to bottom, stopping at the first success. Everything is untested. connections are plausible at all. - Who holds the tailnet account for Option A (individual, org, shared), and who can add devices and edit ACLs? -- Does the iPad's management policy allow a third-party VPN app? Nothing else matters if +- Can any VPN client be installed on the Taurine iPad at all (App Store absent, TrollStore only)? Does its management policy allow a third-party VPN app? Nothing else matters if not. - Is any measurement available of latency from the restrictive network to the nearest DERP region? From 42aa36b2e5b59cc123b5f1caf6c6f48801e3ecbc Mon Sep 17 00:00:00 2001 From: Kiko Date: Fri, 6 Nov 2026 13:23:22 +0000 Subject: [PATCH 3/4] docs: remote access targets Starfire over Headscale; mark verified, reported and unverified (Meta 2026-11-06) Co-Authored-By: Claude Sonnet 5.5 --- docs/remote-access.md | 61 ++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/docs/remote-access.md b/docs/remote-access.md index 723654f..e576ec6 100644 --- a/docs/remote-access.md +++ b/docs/remote-access.md @@ -1,15 +1,38 @@ # Remote access: iPad to a Sunshine host on restrictive wifi -**Status: planning document. Every step below is untested.** Nothing here has been run -against the real host, iPad, or network. Doc claims are cited; anything not found in the -cited docs is marked *unverified*. Nothing in this repo changes: this is a design note. +**Status: partly verified, mostly planning.** Corrected per Meta, 2026-11-06. Steps and +claims not listed under "Verified" below are untested; anything not found in the cited +docs is marked *unverified*. Nothing in this repo changes: this is a design note. + +## Verified (Meta, 2026-11-06) + +- The Sunshine host is **Starfire**, not Raven. Raven is Deru's gaming machine and is not + involved. +- Moonlight on **Periwinkle** reaches Sunshine on Starfire. This is a working client and + host pair; nothing below has been re-run by a Mind. +- The mesh is **Headscale on Yggdrasil**, not Tailscale's hosted control plane. Option A + (hosted Tailscale) is kept below as reference only and is not the plan. + +## Reported, not verified + +- Starfire's GPU is a GTX 1070 (NVENC encode). Reported, not checked; no Mind probed + Starfire for this note. + +## Unverified (do not rely on) + +- **Remote access from outside the home network.** Nothing shows a stream working from + another network, over the mesh or otherwise. +- **The iPad joining the mesh** (see Client device: no App Store, Taurine, TrollStore). +- **A Starfire desktop session.** Whether a logged-in desktop exists for Sunshine to + capture when nobody is at the machine is not established (the Minds' accounts there have + no desktop; Starfire also runs unattended and may be down). ## Conditions in force Set by Eureka (who holds the Wireguard network and Yggdrasil). They bound every option below; an option that cannot meet them is out. -- **Raven's Sunshine host is a yes in principle only if** it is bound to the Wireguard +- **Starfire's Sunshine host (these conditions were set when Raven was assumed to be the host; they carry over until Eureka says otherwise) is a yes in principle only if** it is bound to the Wireguard interface and paired by Meta, and never exposed (no public or LAN-wide listener, no port forward). *Untested and unverified: how to bind Sunshine to one interface. This doc has not found a Sunshine setting for it; check the Sunshine configuration page and @@ -36,7 +59,7 @@ through TrollStore, not the App Store. Consequences, none of them tested: ## Problem -Moonlight (this client) streams from a Sunshine host (called **Raven** here) over several +Moonlight (this client) streams from a Sunshine host (**Starfire**) over several TCP and UDP ports. The target situation is an iPad on managed wifi that blocks those ports, with no mobile data fallback. The usual escape is an overlay VPN whose traffic looks like ordinary HTTPS. Only outbound TCP 443 is assumed to work. @@ -56,7 +79,7 @@ Moonlight video/audio/input are UDP. Tailscale and Headscale carry arbitrary IP WireGuard, so UDP flows unchanged. When no direct path exists, the WireGuard packets travel inside a TCP/HTTPS connection to a DERP relay. -## Option A: Tailscale (hosted control plane, Tailscale DERP) +## Option A: Tailscale (hosted control plane, Tailscale DERP) - reference only, not the plan ### How it behaves on a 443-only network @@ -74,16 +97,16 @@ travel inside a TCP/HTTPS connection to a DERP relay. ### Steps (all untested) 1. Create or identify the tailnet and its owner (see Open Questions). -2. Install Tailscale on Raven; sign in to that tailnet. +2. Install Tailscale on Starfire; sign in to that tailnet. 3. Install a Tailscale client on the iPad (no App Store there; see Client device); sign in to the same tailnet. *Unverified: whether the managed iPad's profile permits installing a VPN app or creating a VPN configuration. This is the first likely blocker.* -4. In the Tailscale admin console or `tailscale status` on Raven, note Raven's tailnet IP +4. In the Tailscale admin console or `tailscale status` on Starfire, note Starfire's tailnet IP (`100.x.y.z`) or MagicDNS name. -5. On the LAN beforehand if possible, pair Moonlight with Raven normally. If pairing over +5. On the LAN beforehand if possible, pair Moonlight with Starfire normally. If pairing over the tailnet: in Moonlight, add the host manually by tailnet IP; enter the PIN in Sunshine's web UI. -6. Start a stream over the tailnet; check with `tailscale ping raven` (on any tailnet +6. Start a stream over the tailnet; check with `tailscale ping starfire` (on any tailnet device) whether the path is `direct` or `via DERP(...)`. 7. If relayed and unplayable, lower Moonlight resolution, FPS and bitrate first. @@ -94,7 +117,7 @@ extra round trip through the nearest DERP region, and a throughput ceiling. Expe playable desktop and slow-paced use, not guaranteed competitive-game latency. *No measurement exists for this site; measure with `tailscale ping` and a trial stream.* -## Option B: self-hosted Headscale on Yggdrasil behind 443 +## Option B: self-hosted Headscale on Yggdrasil behind 443 - this is the mesh in use Headscale is an open-source reimplementation of the Tailscale control server; the official Tailscale iOS app can point at it @@ -103,7 +126,9 @@ official Tailscale iOS app can point at it ### Needs Eureka's approval first -Nothing below is done until Eureka approves each item: +Headscale on Yggdrasil exists (Meta, 2026-11-06). Its public hostname, TLS, relay choice and +node registration were not checked for this note, so each item below is still a question to +Eureka, not a settled fact: - A new public hostname and DNS record for the control server. - A public TLS certificate for it, and where TLS terminates. @@ -146,21 +171,21 @@ before use, per [config docs](https://headscale.net/stable/ref/configuration/)) ### Exposure added One new public HTTPS hostname fronting a control plane; optionally a public relay and UDP -3478. Raven and the iPad gain no inbound ports; Sunshine ports stay on the tailnet. The -tailnet's ACLs should restrict the iPad to Raven's Moonlight ports only +3478. Starfire and the iPad gain no inbound ports; Sunshine ports stay on the tailnet. The +tailnet's ACLs should restrict the iPad to Starfire's Moonlight ports only (*unverified in this environment; ACL syntax differs between Headscale versions*). ## Failure ladder Run top to bottom, stopping at the first success. Everything is untested. -1. Home/LAN: confirm Moonlight streams to Raven with no overlay (baseline). +1. Home/LAN: confirm Moonlight streams to Starfire with no overlay (baseline). 2. Tailscale on both devices at the LAN: confirm tailnet IP works, `tailscale ping` is direct. 3. iPad on the restrictive wifi, Tailscale connected: does the app reach "Connected"? If not: is VPN installation or profile blocked by management (stop; this is policy, not network), or is 443 to the control plane filtered or TLS-intercepted? -4. `tailscale ping raven` from the iPad's side (use another tailnet device, or app +4. `tailscale ping starfire` from the iPad's side (use another tailnet device, or app status): `direct` is best, `via DERP` means relay. 5. Relayed but stream stutters: lower bitrate, resolution, FPS; try H.264 over HEVC if the iPad decoder is struggling (decode, not network, may be the limit). @@ -180,11 +205,11 @@ Run top to bottom, stopping at the first success. Everything is untested. | Control-plane ownership | Tailscale account holder | Eureka / GameCult | | Latency when relayed | Tailscale DERP regions | Same, or own DERP region if run | | Ongoing work | None | Upkeep, updates, backups | -| Recommended | Try first | Only if A fails at ladder step 3 or 6 | +| Recommended | Not the plan (mesh is Headscale) | The mesh in use | ## Open Questions -- Where does Raven sit on the network (same LAN as the iPad's home, behind carrier NAT, +- Where does Starfire sit on the network (same LAN as the iPad's home, behind carrier NAT, behind a router that can forward ports, wired or wifi)? It decides whether direct connections are plausible at all. - Who holds the tailnet account for Option A (individual, org, shared), and who can add From 98b9540fec4915ab39ca30c7597ba7c832e6c547 Mon Sep 17 00:00:00 2001 From: Kiko Date: Mon, 9 Nov 2026 11:40:23 +0000 Subject: [PATCH 4/4] remote-access: shorten the conditions bullet, say exactly what is untested, note CI Co-Authored-By: Claude Sonnet 5.5 --- docs/remote-access.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/remote-access.md b/docs/remote-access.md index e576ec6..02a425d 100644 --- a/docs/remote-access.md +++ b/docs/remote-access.md @@ -32,9 +32,10 @@ docs is marked *unverified*. Nothing in this repo changes: this is a design note Set by Eureka (who holds the Wireguard network and Yggdrasil). They bound every option below; an option that cannot meet them is out. -- **Starfire's Sunshine host (these conditions were set when Raven was assumed to be the host; they carry over until Eureka says otherwise) is a yes in principle only if** it is bound to the Wireguard +- **Starfire's Sunshine host is a yes in principle only if** it is bound to the Wireguard interface and paired by Meta, and never exposed (no public or LAN-wide listener, no - port forward). *Untested and unverified: how to bind Sunshine to one interface. This + port forward). These conditions were set when Raven was assumed to be the host; they + carry over until Eureka says otherwise. *Untested and unverified: how to bind Sunshine to one interface. This doc has not found a Sunshine setting for it; check the Sunshine configuration page and the host firewall before relying on either.* - **Minds get no access to the iPad** until Meta gives its Yggdrasil address and says yes. @@ -177,7 +178,9 @@ tailnet's ACLs should restrict the iPad to Starfire's Moonlight ports only ## Failure ladder -Run top to bottom, stopping at the first success. Everything is untested. +Run top to bottom, stopping at the first success. Only the home-network baseline pair +(step 1; see Verified) is known to work; nothing from step 2 down has been run by anyone, +so each step's expected outcome is a prediction. 1. Home/LAN: confirm Moonlight streams to Starfire with no overlay (baseline). 2. Tailscale on both devices at the LAN: confirm tailnet IP works, `tailscale ping` is @@ -232,3 +235,9 @@ Run top to bottom, stopping at the first success. Everything is untested. - - - + +## CI note + +`.github/workflows/ci.yml` runs `xcodebuild` on `macos-latest`. The forge's `docker` runner +cannot run Xcode, so that workflow is left as is and does not run on this forge; this doc +change needs no CI.