Replace patchelf crack with Freeloader LD_PRELOAD approach
- Multi-stage Dockerfile: discover patterns from PMS binary (capstone), compile .so with zig (musl), layer onto lscr.io/linuxserver/plex - Uses LD_PRELOAD instead of patchelf (which corrupts Plex's musl loader) - Auto-discovery: broad structural patterns with string-anchored fallback (//feature) and relationship-based fallback (BITSET_REF within BS_INIT) - hook.cpp uses __has_include for generated patterns with hardcoded fallbacks - Custom wrapper.sh (no traffic_logger preload) - Vendored Freeloader source (github.com/authrequest/Freeloader, AGPL-3.0) - Removed stale plexmediaserver_crack.so binary - Supports Plex 1.43.3+ (verified against 1.43.2 and 1.43.3)
This commit is contained in:
1 parent
4399a8288d
commit
72f4661bdc
72 files changed
+77927
-17
No files matched your search
@@ -0,0 +1,172 @@
|
||||
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
||||
# Plex over Tailscale / Headscale (no code patching)
|
||||
|
||||
Make a local Plex server reachable by remote users through a **mesh VPN** instead
|
||||
of Plex Relay, router port-forwarding, or a binary patch. Every device that joins
|
||||
your tailnet reaches the Plex host's private tailnet IP directly; WireGuard
|
||||
encrypts the transport end-to-end.
|
||||
|
||||
```
|
||||
Remote client (Tailscale) ─────WireGuard────▶ Plex host (Tailscale) 100.x.y.z:32400
|
||||
│ ▲
|
||||
└── learns the server from plex.tv, which now publishes
|
||||
the custom URL https://100.x.y.z:32400
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layout & design
|
||||
|
||||
```
|
||||
plex-tailnet/
|
||||
├── plex-tailscale-setup.sh # run on the Plex host: VPN + Plex config + healthcheck
|
||||
├── headscale-server-setup.sh # run on a VPS (optional): self-hosted control plane
|
||||
├── lib/
|
||||
│ ├── common.sh # shared shell helpers (sourced, never executed)
|
||||
│ └── plex_prefs.py # Preferences.xml read/merge (XML lives here, not in bash)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
Principles applied:
|
||||
|
||||
- **Low coupling / high cohesion.** Generic concerns (colour logging, dry-run
|
||||
execution, prompts, root/command guards, secret redaction) live once in
|
||||
`lib/common.sh`; each script keeps only its own orchestration. All XML editing
|
||||
is isolated in `lib/plex_prefs.py` — cohesive, reviewable, and testable on its
|
||||
own (`plex_prefs.py merge|get`), so the shell never hand-parses XML.
|
||||
- **Fail fast, located.** `set -euo pipefail` plus an `ERR` trap that reports the
|
||||
failing line; tolerated failures are explicitly guarded (`|| true` / `|| warn`).
|
||||
- **Idempotent & reversible.** Re-runs merge (never duplicate) settings; every
|
||||
`Preferences.xml` change is preceded by a timestamped backup and ownership is
|
||||
restored afterwards. `--dry-run` previews every action and changes nothing.
|
||||
- **Secret hygiene.** Auth keys are redacted in logs **and** never routed through
|
||||
the dry-run echo. The `headscale` pre-auth key is printed once, labelled as a
|
||||
secret.
|
||||
|
||||
| Component | Runs on | Responsibility |
|
||||
| --- | --- | --- |
|
||||
| `plex-tailscale-setup.sh` | Plex host (Linux/systemd) | install/join Tailscale, security questionnaire, edit `Preferences.xml`, firewall, healthcheck |
|
||||
| `headscale-server-setup.sh` | public VPS *(optional)* | install + configure Headscale, create user, mint pre-auth key |
|
||||
| `lib/common.sh` | sourced | logging, `run` (dry-run), prompts, guards, redaction |
|
||||
| `lib/plex_prefs.py` | invoked | merge/read `Preferences.xml` attributes |
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
### A) Tailscale's control plane (simplest; free for up to 3 users)
|
||||
|
||||
```bash
|
||||
sudo ./plex-tailscale-setup.sh # interactive login (prints a URL)
|
||||
sudo ./plex-tailscale-setup.sh --authkey tskey-auth-xxxxx # unattended
|
||||
```
|
||||
|
||||
Each remote user installs Tailscale (https://tailscale.com/download), joins the
|
||||
same tailnet, opens Plex — done.
|
||||
|
||||
### B) Your own Headscale (no user limits, full control)
|
||||
|
||||
On a public VPS with a DNS name and ports 80/443 open:
|
||||
|
||||
```bash
|
||||
sudo ./headscale-server-setup.sh --domain hs.example.com --user plex # prints a key
|
||||
```
|
||||
|
||||
On the Plex host and every client:
|
||||
|
||||
```bash
|
||||
sudo tailscale up --login-server https://hs.example.com --authkey <preauth-key>
|
||||
# Plex host can do VPN + Plex config in one go:
|
||||
sudo ./plex-tailscale-setup.sh --login-server https://hs.example.com --authkey <preauth-key>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What the Plex script changes
|
||||
|
||||
`Preferences.xml` is edited **while Plex is stopped** (Plex overwrites it on
|
||||
shutdown), via `lib/plex_prefs.py`, after a backup, with ownership restored:
|
||||
|
||||
| Attribute | Change | Why |
|
||||
| --- | --- | --- |
|
||||
| `customConnections` | append `https://<tailscale-ip>:32400` | plex.tv publishes the tailnet address for discovery |
|
||||
| `LanNetworksBandwidth` | append `100.64.0.0/10`, `fd7a:115c:a1e0::/48` | treat the tailnet as **LAN**: full quality, no remote throttle |
|
||||
| `secureConnections` | your choice (default Preferred) | clean connect over the already-encrypted tunnel |
|
||||
| `RelayEnabled` | `0` (if you disable Relay) | stop bouncing through Plex's relay once on the tailnet |
|
||||
|
||||
### Security questionnaire (interactive)
|
||||
|
||||
On a terminal the script asks three questions (each has a safe default; pass the
|
||||
flag — or `--yes` — to skip the prompt):
|
||||
|
||||
| Prompt | Flag(s) | Default | Effect |
|
||||
| --- | --- | --- | --- |
|
||||
| Secure connections mode | `--secure required\|preferred\|disabled\|keep` | preferred | `secureConnections` |
|
||||
| Disable Plex Relay? | `--disable-relay` / `--keep-relay` | disable | `RelayEnabled=0` |
|
||||
| Firewall lockdown of `32400/tcp` | `--firewall none\|tailnet\|lan` | none | see below |
|
||||
|
||||
**Firewall lockdown** restricts Plex's port to the VPN (`tailnet`) or VPN + RFC1918
|
||||
LAN (`lan`). It only ever touches `32400/tcp` (SSH stays open), acts only on an
|
||||
**already-active** ufw/firewalld (never enables a firewall — that risks an SSH
|
||||
lockout), and otherwise prints an equivalent `nftables` snippet.
|
||||
|
||||
### Health check
|
||||
|
||||
Runs after install, and standalone with `sudo ./plex-tailscale-setup.sh
|
||||
--healthcheck` (**no changes, no root**). PASS/WARN/FAIL for: Tailscale backend +
|
||||
tailnet IP, the Plex service, Plex's local API, Plex reachable at its tailnet IP,
|
||||
the `customConnections` / LAN-networks / Relay values Plex actually persisted, and
|
||||
the firewall posture.
|
||||
|
||||
Other flags: `--prefs PATH` (quote it), `--service`, `--port`, `--url-scheme`,
|
||||
`--ts-iface`, `--skip-tailscale`, `--skip-plex`, `--dry-run`.
|
||||
|
||||
**Prerequisites:** Plex installed, **claimed**, owner signed in; Linux + systemd;
|
||||
`python3` + `python3-defusedxml` + `curl`; run as root (except `--healthcheck`).
|
||||
|
||||
---
|
||||
|
||||
## Letting other people in
|
||||
|
||||
1. They install Tailscale and join your tailnet/Headscale (a per-user reusable
|
||||
pre-auth key from `headscale preauthkeys create` is the easy path).
|
||||
2. In Plex, **Settings → Users & Sharing**, share the libraries with their Plex
|
||||
account.
|
||||
3. They sign into Plex; the server shows up over the tailnet.
|
||||
|
||||
### Lock guests to the Plex port with ACLs (recommended)
|
||||
|
||||
Headscale (`/etc/headscale/acl.hujson`, referenced by `policy.path`):
|
||||
|
||||
```hujson
|
||||
{
|
||||
"groups": { "group:plexusers": ["alice@", "bob@"] },
|
||||
"hosts": { "plexserver": "100.64.0.5/32" },
|
||||
"acls": [
|
||||
{ "action": "accept", "src": ["group:plexusers"], "dst": ["plexserver:32400"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Tailscale's admin console (Access Controls) uses the equivalent `acls`/`tagOwners`.
|
||||
|
||||
---
|
||||
|
||||
## Caveats
|
||||
|
||||
- **2026 Plex Pass enforcement.** Reports indicate Plex now requires Plex Pass /
|
||||
Remote Watch Pass on the **server account** for *remote* streaming even over
|
||||
Tailscale. `LanNetworksBandwidth` makes Plex treat the tailnet as local (which
|
||||
historically sidestepped the cap and the entitlement gate); if your build still
|
||||
gates, the lever is on the server account, not the client. VPN connectivity
|
||||
works regardless.
|
||||
- **TLS / certificates.** Capable clients reach `https://<ip>:32400` via Plex's
|
||||
auto-generated `plex.direct` hostname (valid cert). For a strict client, use
|
||||
`--secure preferred` (default) or `--url-scheme http`; WireGuard already
|
||||
encrypts the wire.
|
||||
- **Headscale TLS.** `--no-tls` listens on `127.0.0.1:8080` for a reverse proxy;
|
||||
otherwise built-in Let's Encrypt needs ports 80 + 443 reachable.
|
||||
- **POSIX/systemd only.** Targets Debian/Ubuntu-family Plex hosts.
|
||||
|
||||
Interoperability/remote-access tooling for infrastructure you operate yourself.
|
||||
It ships no Plex code and bypasses no account authentication.
|
||||
Reference in new issue
Block a user