liquiflux-xray-api
Docker app from Foehammer's Repository
Overview
Readme
View on GitHubLiquiflux X-Ray server
Shares X-Ray timelines ("who's on screen right now") with the Liquiflux Plex client, at home and through your Cloudflare Tunnel, to anyone who signs in with a Plex account that can use your Plex server. It also scans movies Plex adds, on your NVIDIA GPU, and imports packs made with X-Ray Desktop. Built for Unraid; runs on any Docker host.
Got a big library? Scan it on your PC first. The server's scanner is made to keep up with the movies you add. A first scan of a whole library on a typical server GPU (or on the CPU) can take days to weeks; a recent gaming PC is many times faster. The recommended way to bulk process a library, unless your server has a powerful GPU, is X-Ray Desktop: a Windows app that scans your Plex movies on any NVIDIA, AMD or Intel graphics card and sends the results here as a pack. This server waits for you on first start (step 5 below), so import the pack before it starts scanning; from then on it only scans what's new.
Liquiflux (TV, phone, PC) Unraid / Docker host
───────────────────────── https ┌───────────────────────────────────────────┐
away from home ──► Cloudflare ──► cloudflared ──► :8090 xray-api (public port) │
at home ─────────────────────── http ──────────► :8089 xray-api (LAN port) │
│ reads timelines (read-only)│
▼ │
appdata/liquiflux-xray/timelines │
▲ writes │
xray-scanner (no ports, GPU, Plex token) ────┘
- Sign-in is Plex. Liquiflux already holds your Plex sign-in, so it just works: it swaps it for an X-Ray session (2 hours, renewed by itself) and never sends it anywhere else. The web page at the server's address has a "Sign in with Plex" button (the same flow Seerr uses), mainly so the owner can see the scanner's status and upload packs.
- Who gets in: you, and everyone your Plex server is shared with right now, checked live
with plex.tv at every sign-in. Nothing to maintain. (You can narrow it with
XRAY_ALLOWED_USERSorXRAY_OWNER_ONLY=1, but the default needs neither.) - Why two containers? On purpose, for security. The API is the only part the internet can reach, and it holds nothing worth stealing: no Plex token, no GPU, timelines read-only, a tiny image with no shell. The scanner holds your Plex owner token (full control of your Plex account) and a large Python/CUDA stack, but has no ports at all. If they were one container, a bug in the web-facing code would be one step from your Plex token; split, someone who breaks in through the tunnel gets read-only cast timelines.
- Two ports. 8090 is the public port: sign-in and timelines only. 8089 is the LAN port: it also has the owner's status page and pack upload, only answers private addresses, and refuses anything that came through Cloudflare. Point the tunnel at 8090.
What the two containers can and can't do is in SECURITY.md.
Images
Built from this repository by GitHub Actions (images.yml):
ghcr.io/foehammer1987/liquiflux-xray-api(static Go binary on distroless, ~10 MB)ghcr.io/foehammer1987/liquiflux-xray-scanner(Python + CUDA 12 + face models, ~3 GB)
Tags: latest, the release in VERSION (e.g. 0.9.40), and sha-<commit>.
Needs
- Unraid 6.12 or 7.x (or any Linux Docker host).
- For GPU scanning: the Nvidia Driver plugin (driver 525 or newer). GTX 10-series (Pascal) and newer work. Without a GPU the scanner still runs, slowly, on the CPU.
- Optional: Cloudflare Tunnel already running (for example the official
cloudflare/cloudflaredcontainer). Without it, X-Ray works at home only.
Install on Unraid (templates)
Nothing to prepare: Unraid creates the appdata folders, and the scanner signs in to Plex itself (step 3).
Add the templates. Download them to Unraid's template folder (not
templates-user, where Unraid keeps your saved settings: a copy there makes Edit open blank fields):mkdir -p /boot/config/plugins/dockerMan/templates/liquiflux-xray cd /boot/config/plugins/dockerMan/templates/liquiflux-xray for t in api scanner; do curl -fsSLo liquiflux-xray-$t.xml https://raw.githubusercontent.com/Foehammer1987/liquiflux-xray-server/main/unraid/liquiflux-xray-$t.xml doneThen Docker tab > Add Container > Template: pick each one (under the default templates). When you Apply, Unraid saves your values in
templates-user/my-<name>.xml, and Edit opens those. Downloading the templates again later only adds new fields; it never touches your settings.liquiflux-xray-scanner: set Plex URL (e.g.
192.168.1.10:32400;http://is optional), Movies (your movies share; Movies 2 and Movies 3 for more folders) and Path map (<the path Plex uses>=/media/movies; check a movie in Plex: Get Info > View XML >Part file="..."). Apply. The first pull is about 3 GB. Link it to Plex: open the scanner's log (Docker tab > its icon > Logs). It showsgo to https://plex.tv/link ... and enter ABCD. Open plex.tv/link signed in as the owner of the Plex server and enter the code. The scanner checks the account owns this server, then saves its token inappdata/liquiflux-xray/config/plex_token, readable only by the scanner's user. The token never goes into the template, the container's settings, or the log. (To link again later, delete that file and restart the scanner.) Its log then saysGPU: CUDA (<your GPU>). If it saysCPU, see Troubleshooting. No NVIDIA GPU: remove--runtime=nvidiafrom Extra Parameters.liquiflux-xray-api: set Plex URL. Leave Allowed users empty. Apply.
Open
http://<unraid-ip>:8089(also the scanner's WebUI link) and sign in with Plex as the owner. The scanner is waiting for you: it scans nothing until you either upload a pack (Import a pack; the films the pack doesn't cover are then scanned) or press Start scanning. For a big library, make that pack with X-Ray Desktop on your PC first (see the note at the top); it can also Send it straight to this address. Scan settings there set films at once, decode threads, films per round, how often to look for new films, and the hours scans may run; Pause scanning stops after the films in progress.In Liquiflux: Settings > Discovery > X-Ray Server =
http://<unraid-ip>:8089and Save. It should say "Saved, signed in to X-Ray".
Both templates already run the containers as 99:100 with a read-only root filesystem, all
capabilities dropped, no-new-privileges, and pid/memory/CPU limits (Extra Parameters). Keep
those. The scanner's memory and CPU limits (--memory=8g --cpus=8) can be changed there.
Separate networks (recommended). Compose puts each container on its own network. With the templates, do the same once in the terminal, then pick the network in each template's Network Type (Unraid lists custom networks there):
docker network create liquiflux-xray-api
docker network create liquiflux-xray-scanner
Optional: a secret file instead of plex.tv/link. For a token kept in a folder only root
can open, with tighter folder permissions than Unraid's default, run the setup script
before adding the scanner, then set the scanner's Plex token file (advanced view) to
/mnt/user/appdata/liquiflux-xray/secrets/plex_token:
curl -fsSLo /tmp/xray-setup.sh https://raw.githubusercontent.com/Foehammer1987/liquiflux-xray-server/main/unraid-setup.sh
less /tmp/xray-setup.sh # read it first
bash /tmp/xray-setup.sh /mnt/user/appdata/liquiflux-xray
Updating or reinstalling. Unraid only pulls an image it doesn't have yet, so removing
and re-adding a container keeps the old image. To get a new release: Docker tab > click the
container's icon > Force update (or docker pull ghcr.io/foehammer1987/liquiflux-xray-scanner:latest
and the same for -api). The scanner's first log lines name its version.
Install with Docker Compose
git clone https://github.com/Foehammer1987/liquiflux-xray-server
cd liquiflux-xray-server
cp .env.example .env
nano .env # PLEX_URL, XRAY_APPDATA, MOVIES_PATH and XRAY_MAP at least
bash unraid-setup.sh # makes the folders, asks for the Plex token (hidden)
docker compose up -d # pulls the images; add --build to build them here instead
docker compose logs -f xray-scanner
Unraid's Compose Manager plugin works too: point a stack at the cloned folder.
Timelines you already have
Packs from X-Ray Desktop do this through the upload on the web page. To copy files by hand:
put timeline *.json files in appdata/liquiflux-xray/timelines, an overrides.txt in
appdata/liquiflux-xray/config/, and a scanner cache/people folder in
appdata/liquiflux-xray/cache/people (saves re-reading actors' photos). Then:
chown -R 99:100 /mnt/user/appdata/liquiflux-xray/{timelines,config,cache}.
Cloudflare Tunnel
In the Cloudflare dashboard: Zero Trust > Networks > Tunnels > your tunnel > Public hostnames > Add:
- Subdomain:
xray(any), your domain - Service: HTTP, URL:
<unraid-ip>:8090
If your cloudflared container is on its own Docker network, connect it to the API's network
and use liquiflux-xray-api:8090, e.g. docker network connect liquiflux-xray-api cloudflared
(Compose names that network liquiflux-xray_xray-api).
Then on devices away from home, X-Ray Server = https://xray.yourdomain. Liquiflux only sends
a Plex sign-in over https (or plain http on your own network), so an http:// address
outside your network gets nothing.
Recommended on the Cloudflare side (all free):
- SSL/TLS > Edge Certificates: Always Use HTTPS on, Minimum TLS 1.2.
- Security > WAF > Rate limiting rules: one rule, URI path starts with
/api/v1/auth/, 20 requests per 10 seconds per IP, block for 10 seconds. (The server limits sign-ins itself too; this stops floods before they reach your house.) - Don't put Cloudflare Access in front of this hostname: Liquiflux can't do its login page. Plex sign-in is the gate.
- Set
XRAY_TRUSTED_PROXIESon the API to your cloudflared container's address.
Settings
Template fields and .env (Compose) use the same names.
| Setting | Default | What |
|---|---|---|
PLEX_URL |
(required) | Your Plex server on the LAN, e.g. http://192.168.1.10:32400 |
XRAY_APPDATA |
(Compose, required) | Folder for timelines, cache, status, packs and the token secret |
MOVIES_PATH, XRAY_MAP |
/mnt/user/movies, /movies=/media/movies |
Read films from disk. XRAY_MAP is <Plex's path>=<container path>, one per movies folder, joined with ; (e.g. /movies=/media/movies;/movies4k=/media/movies2). Films outside every mapped folder are read through Plex (slower, shows as a Plex stream) |
XRAY_ALLOWED_USERS |
everyone with access | Leave empty. Or a comma list of Plex usernames or account ids |
XRAY_OWNER_ONLY |
0 |
1 = only the Plex server's owner |
XRAY_SESSION_HOURS |
2 |
How long a sign-in lasts (1-168). Removing someone's Plex access cuts them off within this time |
XRAY_LAN_PORT, XRAY_PUBLIC_PORT |
8089, 8090 |
Host ports |
XRAY_TRUSTED_PROXIES |
private ranges | Who may say which address a visitor came from (Cf-Connecting-Ip). Set it to cloudflared's address to be strict |
XRAY_LAN_CIDRS |
private ranges + 100.64/10 | Who the LAN port answers |
XRAY_SECTIONS |
auto |
Movie libraries to scan: auto = all, or Plex section ids |
XRAY_INTERVAL_MIN, XRAY_BATCH |
30, 20 |
How often to look for new movies, and how many per round (starting values; the status page's Scan settings override them, as for the next three rows) |
XRAY_HOURS |
empty | Only scan between these hours, e.g. 01-07; empty = any time |
XRAY_WORKERS, XRAY_READERS |
1, 3 |
Scanner parallelism (1-2 workers suit a GPU shared with Plex) |
XRAY_CPUS, XRAY_MEMORY |
8, 8g |
Container limits for the scanner (templates: Extra Parameters) |
XRAY_REQUIRE_GPU |
0 |
1 = stop with an error instead of falling back to the CPU |
NVIDIA_VISIBLE_DEVICES |
all |
A GPU UUID (nvidia-smi -L) to pin one GPU |
XRAY_VERSION |
latest |
(Compose) image tag, e.g. 0.9.40 |
How new movies get scanned
Once scanning is on (see step 5), every XRAY_INTERVAL_MIN minutes the scanner lists Plex's movie libraries (one request each),
picks up to XRAY_BATCH movies with no timeline that were added over 15 minutes ago (so Plex
has matched them), and scans them at low priority, matching faces only against each film's
own Plex cast list. Animated films are detected from their frames and get the cast list only.
A film that fails is tried again after 1 h, 2 h, 4 h... up to daily. Once a day, films skipped
as "nomatch" or "nophotos" are looked at again (rescanned only if Plex's cast for them
changed, e.g. after you fixed the match). Packs dropped into appdata/liquiflux-xray/import/
are imported within a minute.
config/overrides.txt fixes titles the animation check gets wrong:
<ratingKey> animated or <ratingKey> live, one per line.
Packs from X-Ray Desktop
X-Ray Desktop is the fast way to scan a whole library: it runs the same
scanner on your PC's graphics card (NVIDIA, AMD or Intel, through DirectML) and its
Send button uploads the pack to http://<server-ip>:8089 for you. Otherwise:
upload on the web page (LAN, signed in as the owner), or drop the .xrpack in
appdata/liquiflux-xray/import/. Results: import/done/<name>.result.json or
import/failed/.... A pack must be made for this Plex server, by the same scanner version;
a timeline replaces the server's only when it's newer. Name a pack *.replace.xrpack to
replace regardless.
API (for apps)
POST /api/v1/auth/plex {"plexToken":"..."} |
-> {"token","expiresAt","user":{"id","username","owner"}}. 401 bad token, 403 no access, 429 too many tries |
GET /api/v1/timeline/<ratingKey> + Authorization: Bearer <token> |
the timeline (no title, guid or scan stats), 404 if not scanned |
GET /api/v1/me |
who the session is |
POST /api/v1/auth/logout |
ends the session |
LAN port, owner only: GET /api/v1/status, POST /api/v1/admin/import (raw zip body), GET /api/v1/admin/import/<id> |
Troubleshooting
GPU: CPU (CUDA not working ...): checknvidia-smiworks in the Unraid terminal, that the Nvidia Driver plugin is installed and Docker restarted after it, and that the scanner runs with--runtime=nvidia. GTX 10-series cards need this image's CUDA 12 build; never "upgrade" onnxruntime-gpu past 1.26 or cuDNN past 9.10 (they dropped Pascal).- Scanner keeps showing a plex.tv/link code: enter it at plex.tv/link while signed in as the Plex server's owner. Another account is refused (the log says so) and a new code appears. Codes are replaced every 14 minutes.
- Liquiflux says "Saved, but X-Ray: ...": the message is the server's reason. "no access to this server" means that Plex account isn't shared on this server.
- The api log warns "Plex accepts requests from this container without a valid token": Plex > Settings > Network > "List of IP addresses and networks that are allowed without auth" covers the Docker network. Then only plex.tv sign-ins work (owner and Plex-account tokens), not shared users' server tokens. Remove the Docker range from that list.
- A shared user can't get in: Liquiflux 0.9.39 and later sign in with the plex.tv
account token, which the server checks with plex.tv. An older Liquiflux (or a pasted
token) only has a server token; that's checked with your Plex server directly, which needs
PLEX_URLreachable from the API container. Signing in to Plex again in Liquiflux's Settings fixes it.
License
MIT, see LICENSE.
Requirements
Related apps
Explore more like this
Explore allDetails
ghcr.io/foehammer1987/liquiflux-xray-api:latestRuntime arguments
- Web UI
http://[IP]:[PORT:8089]/- Network
bridge- Shell
sh- Privileged
- false
- Extra Params
--restart=unless-stopped --user 99:100 --read-only --cap-drop=ALL --security-opt=no-new-privileges:true --pids-limit=128 --memory=256m --cpus=1
Template configuration
Sign-in, timelines, and the owner's status page and pack upload. Answers private addresses only and refuses Cloudflare traffic. Never point a tunnel at it.
- Target
- 8089
- Default
- 8089
- Value
- 8089
Sign-in and timelines only. Point Cloudflare Tunnel (HTTP) at [unraid-ip]:this port.
- Target
- 8090
- Default
- 8090
- Value
- 8090
Your Plex server on the LAN, e.g. 192.168.1.10:32400 (http:// is added if you leave it out; use https:// if your Plex needs it).
- Target
- PLEX_URL
Timelines the scanner writes (read-only here).
- Target
- /data/timelines
- Default
- /mnt/user/appdata/liquiflux-xray/timelines
- Value
- /mnt/user/appdata/liquiflux-xray/timelines
Scanner status (read-only here).
- Target
- /data/status
- Default
- /mnt/user/appdata/liquiflux-xray/status
- Value
- /mnt/user/appdata/liquiflux-xray/status
Uploaded X-Ray packs wait here for the scanner.
- Target
- /data/import
- Default
- /mnt/user/appdata/liquiflux-xray/import
- Value
- /mnt/user/appdata/liquiflux-xray/import
How long a sign-in lasts (1-168). Removing someone's Plex access cuts them off within this time.
- Target
- XRAY_SESSION_HOURS
- Default
- 2
- Value
- 2
Leave empty: everyone your Plex server is shared with. Or a comma list of Plex usernames / account ids.
- Target
- XRAY_ALLOWED_USERS
1 = only the Plex server's owner may sign in.
- Target
- XRAY_OWNER_ONLY
- Default
- 0
- Value
- 0
Optional: your Plex machineIdentifier. Empty = asked from Plex at start.
- Target
- PLEX_SERVER_ID
Addresses allowed to send Cf-Connecting-Ip. Empty = private ranges. Set it to your cloudflared container's IP to be strict.
- Target
- XRAY_TRUSTED_PROXIES
Who the LAN port answers. Empty = private ranges + 100.64.0.0/10.
- Target
- XRAY_LAN_CIDRS