plex: placed state, the build ace runs, its own name from the route, all eight libraries read-only #162

Open
mesh-admin wants to merge 2 commits from feat/plex-for-ace into main
Contributor

The manifest named /services/plex/{config,transcode} with owner and mode.
On ace /services/plex is a link to /mnt/plex, plex's 133 GB of state, so
a take would have chmod'ed 0700 the top of the one directory the operator
ruled must never be re-moded or re-owned. The directories are now pathless
(config, data, transcode), placed by the mesh; on an adopted machine they
must be placed where the data is (hq 153) before plex is ever taken.

  • The container sees exactly the paths ace's plex sees today: /config,
    /data (HAL mounts it; the catalogue did not), /transcode and all eight
    libraries, including sport-games, live-shows and formula-1. A library
    whose mount disappears is emptied by Plex's automatic trash emptying,
    taking its watch state with it.
  • Libraries are mounted read-only, as the accesses already said.
  • Owner 1000:1000 and mode 0755: plex runs as uid 1000 (the image reads
    PLEX_UID, not the PUID HAL passes), pms-docker leaves its dirs 0755, and
    ace's /mnt/plex is 1000:1000 0755 - so placing adopted data is a no-op.
  • Image pinned to what ace runs, 1.43.4.10903; the old pin was 1.43.3.
  • ADVERTISE_IP comes from the route's own public name through an env-file
    (${bound:route:name}); it depends on mesh-controller #149, and without
    it the declaration is refused, not applied.
  • The server is routed (label plex) and declares its four GDM discovery
    ports, which LAN players use.
  • No token secret: a minted one is not a Plex token and the sidecar
    preferred it. The sidecar reads PlexOnlineToken from Preferences.xml
    through its read-only config mount, and dials ${port:32400}.

Verified: catalogue tests with MESH_CATALOGUE (parse, mounts); a scratch
resolution with ace's assignment on the #149 controller renders
ADVERTISE_IP=https://plex.zurag.be/, both route names and 32400/tcp +
GDM/udp open to anywhere; on main it is refused naming "name". A
throwaway pms-docker at the pinned digest on empty dirs answered
/identity, wrote customConnections from the env-file and ran as 1000;
client.ts typechecks strict and found the token in a Preferences.xml.

Depends on mesh-controller #149 (${bound:route:name}). Without it the declaration is refused, not applied.

Do not assign on ace before hq 153 lands. The config, data and transcode directories must be placed at /mnt/plex/{config,data,temp} (133 GB, held in place, never copied), and the accesses at /storage/media/*. The assignment draft is ace-assignments/plex.json in the migration repo: endpoints stream (32400, label plex) and gdm-1..4 (public), plus expose 32400 anywhere. The routed port uses expose because a routed endpoint's reach sets names only, and LAN players and the router's forward dial 32400 directly.

The manifest named /services/plex/{config,transcode} with owner and mode. On ace /services/plex is a link to /mnt/plex, plex's 133 GB of state, so a take would have chmod'ed 0700 the top of the one directory the operator ruled must never be re-moded or re-owned. The directories are now pathless (config, data, transcode), placed by the mesh; on an adopted machine they must be placed where the data is (hq 153) before plex is ever taken. - The container sees exactly the paths ace's plex sees today: /config, /data (HAL mounts it; the catalogue did not), /transcode and all eight libraries, including sport-games, live-shows and formula-1. A library whose mount disappears is emptied by Plex's automatic trash emptying, taking its watch state with it. - Libraries are mounted read-only, as the accesses already said. - Owner 1000:1000 and mode 0755: plex runs as uid 1000 (the image reads PLEX_UID, not the PUID HAL passes), pms-docker leaves its dirs 0755, and ace's /mnt/plex is 1000:1000 0755 - so placing adopted data is a no-op. - Image pinned to what ace runs, 1.43.4.10903; the old pin was 1.43.3. - ADVERTISE_IP comes from the route's own public name through an env-file (${bound:route:name}); it depends on mesh-controller #149, and without it the declaration is refused, not applied. - The server is routed (label plex) and declares its four GDM discovery ports, which LAN players use. - No token secret: a minted one is not a Plex token and the sidecar preferred it. The sidecar reads PlexOnlineToken from Preferences.xml through its read-only config mount, and dials ${port:32400}. Verified: catalogue tests with MESH_CATALOGUE (parse, mounts); a scratch resolution with ace's assignment on the #149 controller renders ADVERTISE_IP=https://plex.zurag.be/, both route names and 32400/tcp + GDM/udp open to anywhere; on main it is refused naming "name". A throwaway pms-docker at the pinned digest on empty dirs answered /identity, wrote customConnections from the env-file and ran as 1000; client.ts typechecks strict and found the token in a Preferences.xml. **Depends on mesh-controller #149** (`${bound:route:name}`). Without it the declaration is refused, not applied. **Do not assign on ace before hq 153 lands.** The config, data and transcode directories must be placed at /mnt/plex/{config,data,temp} (133 GB, held in place, never copied), and the accesses at /storage/media/*. The assignment draft is ace-assignments/plex.json in the migration repo: endpoints stream (32400, label plex) and gdm-1..4 (public), plus expose 32400 anywhere. The routed port uses `expose` because a routed endpoint's reach sets names only, and LAN players and the router's forward dial 32400 directly.
mesh-admin added 1 commit 2026-09-30 09:58:48 +00:00
The manifest named /services/plex/{config,transcode} with owner and mode.
On ace /services/plex is a link to /mnt/plex, plex's 133 GB of state, so
a take would have chmod'ed 0700 the top of the one directory the operator
ruled must never be re-moded or re-owned. The directories are now pathless
(config, data, transcode), placed by the mesh; on an adopted machine they
must be placed where the data is (hq 153) before plex is ever taken.

- The container sees exactly the paths ace's plex sees today: /config,
  /data (HAL mounts it; the catalogue did not), /transcode and all eight
  libraries, including sport-games, live-shows and formula-1. A library
  whose mount disappears is emptied by Plex's automatic trash emptying,
  taking its watch state with it.
- Libraries are mounted read-only, as the accesses already said.
- Owner 1000:1000 and mode 0755: plex runs as uid 1000 (the image reads
  PLEX_UID, not the PUID HAL passes), pms-docker leaves its dirs 0755, and
  ace's /mnt/plex is 1000:1000 0755 - so placing adopted data is a no-op.
- Image pinned to what ace runs, 1.43.4.10903; the old pin was 1.43.3.
- ADVERTISE_IP comes from the route's own public name through an env-file
  (${bound:route:name}); it depends on mesh-controller #149, and without
  it the declaration is refused, not applied.
- The server is routed (label plex) and declares its four GDM discovery
  ports, which LAN players use.
- No token secret: a minted one is not a Plex token and the sidecar
  preferred it. The sidecar reads PlexOnlineToken from Preferences.xml
  through its read-only config mount, and dials ${port:32400}.

Verified: catalogue tests with MESH_CATALOGUE (parse, mounts); a scratch
resolution with ace's assignment on the #149 controller renders
ADVERTISE_IP=https://plex.zurag.be/, both route names and 32400/tcp +
GDM/udp open to anywhere; on main it is refused naming "name". A
throwaway pms-docker at the pinned digest on empty dirs answered
/identity, wrote customConnections from the env-file and ran as 1000;
client.ts typechecks strict and found the token in a Preferences.xml.
jschoubben added 1 commit 2026-09-30 11:14:26 +00:00
kometa, tautulli and ombi reached plex by a hand-typed address - a public
name, a HAL network gateway - which the mesh cannot keep true. Plex now
provides plex-api at mesh scope and serves the server's port and scheme, so
the mesh tells each consumer where it is.

The port is written out rather than inferred: plex listens on five ports
(the server and four discovery ones), and the mesh only infers a provision's
port when a module listens on exactly one.

No grants and no provisioner: the credential is the server owner's
X-Plex-Token, which plex.tv issues and the mesh cannot mint. The operator
accepts it as the pair credential for each consumer (ADR 0092); each
consumer's step checks it against the server and writes nothing it refuses.
Author
Contributor

plex now provides plex-api (commit 4213fa7)

  • provides: [{name: plex-api, scope: mesh}], serves: {plex-api: {scheme: http, port: 32400}}. The port is written out: plex listens on five ports, and the mesh only infers a provision's port when a module listens on exactly one.
  • Credential model: accepted, per consumer pair. The credential is the server owner's X-Plex-Token. plex.tv issues it and the mesh cannot mint it, so there are no grants and no provisioner. Until the operator accepts the token for a pair, each consumer gets a value the mesh minted. Plex answers that value with 401, or 400 on a network it trusts. Each consumer's step checks the token against plex and never writes one plex refuses.
  • Consumers: kometa (#159) reads it from its config.yml. tautulli (#151) writes it into config.ini before Tautulli starts. ombi (#156) writes it through ombi's API, in the same step as sonarr, radarr and lidarr.
  • Checked:
    • go test ./internal/catalogue/ passes with all four branches merged, on controller main and on #149. 74 manifests parse.
    • A scratch Resolve/Declaration for ace gives each consumer at=ace.internal, port=32400 and scheme=http, and its own sealed pair credential.
  • Found on the way (controller): plex's node settings (its endpoints and expose) are merged into what it serves, so every consumer's plex-api binding also carries them. This is harmless for these consumers, which read only port and scheme, but it is noise, and a setting named port would redirect consumers. A proposed hq issue is in the report.
**plex now provides `plex-api`** (commit 4213fa7) - `provides: [{name: plex-api, scope: mesh}]`, `serves: {plex-api: {scheme: http, port: 32400}}`. The port is written out: plex listens on five ports, and the mesh only infers a provision's port when a module listens on exactly one. - **Credential model: accepted, per consumer pair.** The credential is the server owner's X-Plex-Token. plex.tv issues it and the mesh cannot mint it, so there are no grants and no provisioner. Until the operator accepts the token for a pair, each consumer gets a value the mesh minted. Plex answers that value with 401, or 400 on a network it trusts. Each consumer's step checks the token against plex and never writes one plex refuses. - **Consumers:** kometa (#159) reads it from its config.yml. tautulli (#151) writes it into config.ini before Tautulli starts. ombi (#156) writes it through ombi's API, in the same step as sonarr, radarr and lidarr. - **Checked:** - `go test ./internal/catalogue/` passes with all four branches merged, on controller main and on #149. 74 manifests parse. - A scratch Resolve/Declaration for ace gives each consumer `at=ace.internal`, `port=32400` and `scheme=http`, and its own sealed pair credential. - **Found on the way (controller):** plex's node settings (its `endpoints` and `expose`) are merged into what it serves, so every consumer's plex-api binding also carries them. This is harmless for these consumers, which read only `port` and `scheme`, but it is noise, and a setting named `port` would redirect consumers. A proposed hq issue is in the report.
You are not authorized to merge this pull request.
This pull request can be merged automatically.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/plex-for-ace:feat/plex-for-ace
git checkout feat/plex-for-ace
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-catalog#162