diff --git a/modules/plex/client.ts b/modules/plex/client.ts index 5aa7e6f..992c14f 100644 --- a/modules/plex/client.ts +++ b/modules/plex/client.ts @@ -58,8 +58,10 @@ export class PlexClient { static fromEnv(env: NodeJS.ProcessEnv = process.env): PlexClient { const url = env.MESH_PLEX_URL ?? `http://127.0.0.1:${env.PLEX_PORT ?? "32400"}`; const dataDir = env.MESH_PLEX_DATA_DIR ?? "/var/lib/plex"; - // The operator-provided token is an own-secret the mesh mounts at MESH_PLEX_TOKEN_FILE (delivered - // by `secret accept`); prefer it, fall back to a bare env var, then to discovery from the data dir. + // The manifest sets neither MESH_PLEX_TOKEN_FILE nor MESH_PLEX_TOKEN: the server already keeps its + // token in Preferences.xml, read here through the manifest's read-only mount of the config dir. + // A token the mesh minted would be one plex.tv never issued, and preferring it would break every + // call, so the module declares no token secret. The file and env overrides stay for hand runs. const token = readSecret(env.MESH_PLEX_TOKEN_FILE) ?? env.MESH_PLEX_TOKEN ?? PlexClient.detectToken(dataDir); if (!token) throw new Error("no Plex token — set MESH_PLEX_TOKEN or make the data dir readable"); return new PlexClient(url, token); diff --git a/modules/plex/module.json b/modules/plex/module.json index 426a70f..71e54a5 100644 --- a/modules/plex/module.json +++ b/modules/plex/module.json @@ -13,8 +13,7 @@ "*.download.completed" ], "own-secrets": { - "broker": "/var/lib/mesh/plex/broker", - "token": "/var/lib/mesh/plex/token" + "broker": "/var/lib/mesh/plex/broker" }, "listens": [ { @@ -22,7 +21,35 @@ "port": 32400, "protocol": "tcp", "from": "mesh", - "why": "streaming and the app; reaching it from outside is a route grant later" + "why": "the server itself: the apps, streaming and the web player, direct and through its route" + }, + { + "name": "gdm-1", + "port": 32410, + "protocol": "udp", + "from": "mesh", + "why": "G'Day Mate discovery: players on the same network find the server without signing in" + }, + { + "name": "gdm-2", + "port": 32412, + "protocol": "udp", + "from": "mesh", + "why": "G'Day Mate discovery" + }, + { + "name": "gdm-3", + "port": 32413, + "protocol": "udp", + "from": "mesh", + "why": "G'Day Mate discovery" + }, + { + "name": "gdm-4", + "port": 32414, + "protocol": "udp", + "from": "mesh", + "why": "G'Day Mate discovery" } ], "accesses": [ @@ -45,6 +72,18 @@ { "path": "/services/media/audiobooks", "mode": "read" + }, + { + "path": "/services/media/sport-games", + "mode": "read" + }, + { + "path": "/services/media/live-shows", + "mode": "read" + }, + { + "path": "/services/media/formula-1", + "mode": "read" } ], "resources": [ @@ -54,39 +93,63 @@ "path": "/var/lib/mesh/plex", "mode": "0700" }, + { + "id": "state", + "type": "directory", + "mode": "0700", + "place": "." + }, { "id": "config", "type": "directory", - "path": "/services/plex/config", - "mode": "0700", + "mode": "0755", + "owner": "1000:1000" + }, + { + "id": "data", + "type": "directory", + "mode": "0755", "owner": "1000:1000" }, { "id": "transcode", "type": "directory", - "path": "/services/plex/transcode", - "mode": "0700", + "mode": "0755", "owner": "1000:1000" }, + { + "id": "server-env", + "type": "file", + "path": "${dir:state}/server.env", + "mode": "0600", + "content": "ADVERTISE_IP=https://${bound:route:name}/\n" + }, { "id": "server", "type": "container", "name": "plex", - "image": "plexinc/pms-docker@sha256:83a425ae9e133b1cb2cc3b809556e01c61cd8ff65c582e41b4374bc2210bac9e", + "image": "plexinc/pms-docker@sha256:e0ab27395614a8e1a4fdf84c6bc60ac664915cfdde70c52d030c7728a1c48e14", "network": "host", "env": { "PLEX_UID": "1000", "PLEX_GID": "1000", "TZ": "Etc/UTC" }, + "env-file": [ + "${dir:state}/server.env" + ], "volumes": [ - "/services/plex/config:/config", - "/services/plex/transcode:/transcode", - "/services/media/movies:/movies", - "/services/media/series:/series", - "/services/media/anime:/anime", - "/services/media/music:/music", - "/services/media/audiobooks:/audiobooks" + "${dir:config}:/config", + "${dir:data}:/data", + "${dir:transcode}:/transcode", + "/services/media/movies:/movies:ro", + "/services/media/series:/series:ro", + "/services/media/anime:/anime:ro", + "/services/media/music:/music:ro", + "/services/media/audiobooks:/audiobooks:ro", + "/services/media/sport-games:/sport-games:ro", + "/services/media/live-shows:/live-shows:ro", + "/services/media/formula-1:/formula-1:ro" ] }, { @@ -96,18 +159,28 @@ "network": "host", "volumes": [ "/var/lib/mesh/plex/broker:/run/secrets/broker:ro", - "/var/lib/mesh/plex/token:/run/secrets/token:ro", - "/services/plex/config:/var/lib/plex/config:ro" + "${dir:config}:/var/lib/plex/config:ro" ], "env": { "MESH_BROKER_FILE": "/run/secrets/broker", - "MESH_PLEX_URL": "http://127.0.0.1:32400", - "MESH_PLEX_TOKEN_FILE": "/run/secrets/token", + "MESH_PLEX_URL": "http://127.0.0.1:${port:32400}", "MESH_PLEX_DATA_DIR": "/var/lib/plex" }, "artifact": "runtime" } ], + "requires": [ + "route" + ], + "contributes": { + "route": { + "label": "plex", + "endpoint": "stream" + } + }, + "binds": { + "route": "${dir:state}/route.json" + }, "build": { "on": [ {