Files
punktfunk/docs-site/content/docs/moonlight.md
T
enricobuehler 23d0452157 feat(host): GameStream opt-in on every route; the native plane is deny(unsafe_code)-enforced
The user direction after WP0: ENet exists only for Moonlight, so the native
plane must be provably safe and the compat planes a deliberate choice.

Opt-in, everywhere. Windows already was (unchecked installer task). The three
opt-out surfaces are flipped: the shipped systemd user unit (deb/RPM/Arch/
sysext) no longer bakes --gamestream into ExecStart — a new
PUNKTFUNK_GAMESTREAM=1 host.env knob (pf-host-config, OR-ed with the CLI
flag) is the packaged opt-in; the NixOS module default goes true→false, with
a module-check assertion that unset = native-only; the Deck installer takes
--gamestream to opt in (--no-gamestream kept as explicit-off). Docs
(quickstart, running-as-a-service, moonlight, ubuntu/fedora/arch firewall
sections, gnome/sway, how-it-works) rewritten to the opt-in shape; the
CHANGELOG carries the upgrade note.

Enforced-safe. punktfunk-core is #![deny(unsafe_code)] crate-wide — every
module that parses network bytes is safe Rust as a compile error, not a
census result. Carve-outs are exactly two documented classes, neither of
which interprets attacker bytes: the client surface (abi, client) and the
transport syscall-batching shims (udp/{apple,linux,windows}, qos_windows).
In punktfunk-host, the modules a secure-default host exposes — native
(cfg-not-test: its tests exercise the client C ABI on purpose),
native_pairing, mgmt, mgmt_token, discovery, wol — are #[forbid(unsafe_code)].

Gates: Linux amd64 container clippy --all-targets -D warnings clean over
core+host-config+host; core 204 tests green under the deny; mgmt 46/46,
control 6/6. .133 Windows clippy (shipped features, clean-first,
sentinel-checked) clean — covers the qos_windows/udp-windows carve-outs.
macOS + iOS cargo check green (the apple.rs carve-out compiles for real).
2026-08-11 20:21:16 +02:00

117 lines
6.2 KiB
Markdown

---
title: Connect with Moonlight
description: Stream from a Punktfunk host using any Moonlight client.
---
Punktfunk speaks the **GameStream** protocol, so [Moonlight](https://moonlight-stream.org/) connects
to it like it would to any GameStream host — no punktfunk-specific app needed. It's a great option for
a browser, a smart TV, or any device without a native client.
> Many platforms also have a **native Punktfunk client** with lower latency and built-in
> discovery/pairing — including **Windows** and **Android** (phone and Android TV). See
> [Clients](/docs/clients) before reaching for Moonlight.
## 1. Make sure the host is running with GameStream enabled
Moonlight needs the GameStream planes, which are **opt-in on every install route** (the default is
the secure native-only host):
- **Linux packages (apt / dnf / pacman / sysext)** — add `PUNKTFUNK_GAMESTREAM=1` to
`~/.config/punktfunk/host.env` and `systemctl --user restart punktfunk-host` — see
[What the unit starts](/docs/running-as-a-service#what-the-unit-starts). (Installs from before
the opt-in change served Moonlight by default; an upgrade switches them to native-only until you
set the knob.)
- **NixOS** — set `services.punktfunk.host.gamestream = true;` (also opens the GameStream firewall
ports).
- **SteamOS / Steam Deck** — run the installer with `--gamestream` (re-running it is safe and
keeps the rest of your config).
- **Windows** — tick the installer's *Enable GameStream (Moonlight) compatibility* checkbox, or
turn it on afterwards from an **elevated** prompt (`=off` puts it back) — see
[Windows Host](/docs/windows-host):
```powershell
punktfunk-host service install --gamestream=on
punktfunk-host service restart
```
- **Running `serve` by hand** — add the flag yourself:
```sh
punktfunk-host serve --gamestream
```
(Bare `serve` is the secure native-only default and stock Moonlight clients can't connect to it; the
native plane is always on, and `--gamestream` adds the Moonlight-compat surface.) GameStream pairs over
plain HTTP and its legacy control encryption is weaker than the native plane's, so only enable it on a
**trusted LAN**. See [Running as a Service](/docs/running-as-a-service) for the bundled unit. The host
advertises itself on the network, so Moonlight usually finds it on its own.
## 2. Add the host in Moonlight
Open Moonlight. Your host should appear automatically on the same network. If it doesn't, use **Add
Host manually** and enter the host machine's IP address.
Still nothing? Two causes account for almost all of it:
- **The GameStream ports aren't open on the host.** They are TCP **47984 / 47989 / 48010** and UDP
**47998 / 47999 / 48000**, plus mDNS on UDP **5353**. On Linux the packages ship a ready-made rule
for your distro's firewall but never enable it:
```sh
sudo ufw allow punktfunk-gamestream # ufw
sudo firewall-cmd --permanent --add-service=punktfunk-gamestream # firewalld
sudo firewall-cmd --reload
```
On Windows the installer adds these rules itself — but only for **Private** and **Domain**
networks, unless you ticked *Allow connections on Public networks* during setup.
- **Another GameStream host is running on the same machine.** Sunshine, Apollo and their forks bind
the same fixed ports and advertise the same mDNS name, so the wrong host answers (or neither
does). Run `punktfunk-host detect-conflicts` on the host machine — it lists any it finds.
Both are covered in more detail in [Troubleshooting](/docs/troubleshooting).
## 3. Pair
Moonlight's PIN is typed in on the **host** side, so you need the host's
[web console](/docs/web-console) running — it's the only UI where a Moonlight PIN can be entered.
1. Open the console at `https://<host-ip>:47992` and go to **Pairing**.
2. In Moonlight, select the host and choose **Pair** — it shows a 4-digit PIN.
3. The console's **Moonlight (GameStream) pairing** card now says a client is waiting. Type the PIN
there and press **Submit PIN**.
The device then appears under **Paired devices**, and Moonlight remembers the host. See
[Pairing & Trust](/docs/pairing) for the full picture.
## 4. Stream
Moonlight lists **Desktop** plus the games the host found installed (Steam, Epic, GOG, Xbox), with
cover art — the same [library](/docs/game-library) the native clients show. Pick one and start
streaming. The host creates a virtual display at the resolution and frame rate Moonlight requests
(set these in Moonlight's settings), encodes it on the GPU, and streams it. Mouse, keyboard, and
controllers flow back to the host — and a Moonlight client that sends pen events, an iPad's Apple
Pencil included, drives the same host-side tablet a native client would, with pressure and tilt
intact (see [Pen and stylus](/docs/input#pen-and-stylus)).
That **Desktop** entry is the operator base list. An `apps.json` in the host's config directory
*replaces* it (it doesn't add to it), so put every entry you want in that file if you write one.
## Tips
- **Set your resolution and frame rate in Moonlight's settings** before connecting — the host matches
whatever Moonlight asks for, creating the virtual display at that exact mode.
- **Codec:** HEVC (H.265) is a good default; AV1 is available if your client supports it.
- **HDR:** Moonlight only offers its HDR toggle when the host advertises a 10-bit codec, which it
does only when [its capture path *and* its encoder](/docs/hdr#the-chain) can really deliver
10-bit BT.2020 PQ. If the toggle is there, turn it on and pick HEVC or AV1 — H.264 stays SDR.
Setting `PUNKTFUNK_10BIT=0` in [`host.env`](/docs/configuration) withdraws the offer entirely; it
is on by default.
- **Bitrate:** start moderate and raise it. For very high bitrates, the [native
clients](/docs/clients) have a built-in speed test; with Moonlight, set the bitrate manually.
- Moonlight uses the GameStream protocol, not Punktfunk's native FEC/encryption extensions. On a
solid LAN this is fine; on a lossy link a [native client](/docs/clients) holds up better.
- Comparing Moonlight's performance overlay with a Punktfunk client's stats HUD? The numbers
measure different slices of the pipeline — see [Understanding the Stats Overlay](/docs/stats)
for a line-by-line comparison matrix before drawing conclusions.