fix(plex): advertise the plex.direct name, not the bare VIP (server vanished from the plex.tv account) #47

Merged
jakman85 merged 1 commits from fix/plex-advertise-plexdirect into main 2026-10-04 07:59:14 +00:00
Owner

The symptom

The Plex server stopped appearing on the plex.tv account. plex.home.keithjackson.dev showed
"not authorized"; connecting straight to the VIP + port worked.

Root cause (measured, not inferred)

customConnections held two hand-typed URLs and neither could ever complete. Because
customConnections overrides Plex's own connection publishing, the server advertised only dead
endpoints. Live Preferences.xml, 2026-10-04:

customConnections="https://plex.home.keithjackson.dev:32400/,https://192.168.2.24:32400/"
ManualPortMappingMode="1"   ManualPortMappingPort=<ABSENT>
Advertised URL Why it cannot work
https://192.168.2.24:32400 The server's cert is *.130072f2….plex.direct with no IP SAN — bare-IP https fails TLS for every client
https://plex.home.keithjackson.dev:32400 Nothing listens. The Gateway serves that host on 443

Not theoretical: Plex's log carries a continuous storm of
CERT: incomplete TLS handshake … sslv3 alert certificate unknown from real LAN clients arriving
through the plex-l4-tcp VIP (source 10.0.1.78 = Cilium identity 1 = reserved:host, i.e. SNAT'd
LAN traffic).

With nothing completable: plex.tv published nothing (MyPlex: mapping state set to 'Mapped - Not Published') → Plex fell back to Plex Relay → the server left the account → clients
reported "not authorized".

The fix

Advertise Plex's own <dashed-ip>.<serverhash>.plex.direct form — public DNS that resolves to the
embedded address, so it points at the LAN VIP and matches the wildcard cert.

Verified with full TLS validation (no curl -k):

https://192-168-2-24.130072f2….plex.direct:32400/identity  ->  200, TLS verify OK
https://192.168.2.24:32400/identity                        ->  000, cert validation FAILED

Declared as ADVERTISE_IP so git owns the value instead of a web form: the image's
40-plex-first-run writes it into customConnections on every pod start, outside the claim guard
(verified in the running image, line 127; /.firstRunComplete is on the overlay fs, not /config).

  • catalog/plex.yaml — ADVERTISE_IP: ${PLEX_ADVERTISE_URL}, replacing the old DEFERRAL note
  • clusters/base/cluster-vars.yaml — new PLEX_ADVERTISE_URL knob (base default is an explicit
    non-functional placeholder; dev is decommissioned)
  • clusters/prod/kustomization.yaml — the prod value
  • apps/wave-0/plex.yaml — re-rendered with the canonical command (two added lines)

A whole-URL cluster-var rather than something derived from ${PLEX_L4_VIP} because envsubst cannot
dash an IP and the hash is certificate identity, derivable from nothing in this tree.

Gate

./infra/devcontainer/dev.sh run bash -c './ci/gate.sh' → rc 3, with spec-immutability,
decisions-log, lint, l0, render-golden, l1, approval-policy all pass. The rc 3 is the
two known environmental skips: gitea-settings (no local SETTINGS_API_TOKEN) and conformance
L2/L3 (CLUSTER_AVAILABLE unset). Not green — reported as rc 3, not as pass.

What this does NOT do

  • It is unreconciled. Nothing here has been observed to put the server back on the account. What
    is proven is that the new URL answers 200 with a valid cert and that every currently advertised URL
    cannot complete.
  • Off-LAN access is not fixed. The advertised address is RFC1918. The WAN tcp/32400 forward
    remains unverified (no NAT hairpin here, so the LAN-side probe is inconclusive, not proof).
  • ManualPortMappingMode="1" with an absent port is left as-is — no GitOps path exists for it
    (the image supports no PLEX_PREFERENCE*), so it is an operator UI step.
  • PiHole resolver behaviour untested. plex.direct resolved from a pod; LAN clients resolve via
    PiHole, and dnsmasq's stop-dns-rebind would reject a public answer carrying a private address.
    This is the most likely way the fix still fails.
  • This env var will overwrite customConnections on the next pod start, deliberately discarding
    anything typed into the web form since.

⚠️ Needs /approve-override

This touches clusters/** (two files), so ci/approval-policy.sh will block it. It needs
/approve-override <reason> posted as a PR review of type "Comment". I have deliberately not
posted it: CLAUDE.md §1 forbids an agent clearing this on its own PR, and the agent account holds
owner, so the prohibition is a rule rather than a limit.

🤖 Generated with Claude Code

## The symptom The Plex server stopped appearing on the plex.tv account. `plex.home.keithjackson.dev` showed "not authorized"; connecting straight to the VIP + port worked. ## Root cause (measured, not inferred) `customConnections` held two hand-typed URLs and **neither could ever complete**. Because `customConnections` *overrides* Plex's own connection publishing, the server advertised only dead endpoints. Live `Preferences.xml`, 2026-10-04: ``` customConnections="https://plex.home.keithjackson.dev:32400/,https://192.168.2.24:32400/" ManualPortMappingMode="1" ManualPortMappingPort=<ABSENT> ``` | Advertised URL | Why it cannot work | |---|---| | `https://192.168.2.24:32400` | The server's cert is `*.130072f2….plex.direct` with **no IP SAN** — bare-IP https fails TLS for every client | | `https://plex.home.keithjackson.dev:32400` | **Nothing listens.** The Gateway serves that host on 443 | Not theoretical: Plex's log carries a continuous storm of `CERT: incomplete TLS handshake … sslv3 alert certificate unknown` from **real LAN clients** arriving through the `plex-l4-tcp` VIP (source `10.0.1.78` = Cilium identity 1 = `reserved:host`, i.e. SNAT'd LAN traffic). With nothing completable: plex.tv published nothing (`MyPlex: mapping state set to 'Mapped - Not Published'`) → Plex fell back to **Plex Relay** → the server left the account → clients reported "not authorized". ## The fix Advertise Plex's own `<dashed-ip>.<serverhash>.plex.direct` form — public DNS that resolves to the embedded address, so it points at the LAN VIP **and** matches the wildcard cert. Verified with full TLS validation (no `curl -k`): ``` https://192-168-2-24.130072f2….plex.direct:32400/identity -> 200, TLS verify OK https://192.168.2.24:32400/identity -> 000, cert validation FAILED ``` Declared as `ADVERTISE_IP` so **git owns the value instead of a web form**: the image's `40-plex-first-run` writes it into `customConnections` on every pod start, outside the claim guard (verified in the running image, line 127; `/.firstRunComplete` is on the overlay fs, not `/config`). - `catalog/plex.yaml` — `ADVERTISE_IP: ${PLEX_ADVERTISE_URL}`, replacing the old DEFERRAL note - `clusters/base/cluster-vars.yaml` — new `PLEX_ADVERTISE_URL` knob (base default is an explicit non-functional placeholder; dev is decommissioned) - `clusters/prod/kustomization.yaml` — the prod value - `apps/wave-0/plex.yaml` — re-rendered with the canonical command (two added lines) A whole-URL cluster-var rather than something derived from `${PLEX_L4_VIP}` because envsubst cannot dash an IP and the hash is certificate identity, derivable from nothing in this tree. ## Gate `./infra/devcontainer/dev.sh run bash -c './ci/gate.sh'` → **rc 3**, with `spec-immutability`, `decisions-log`, `lint`, `l0`, `render-golden`, `l1`, `approval-policy` all **pass**. The rc 3 is the two known environmental skips: `gitea-settings` (no local `SETTINGS_API_TOKEN`) and `conformance` L2/L3 (`CLUSTER_AVAILABLE` unset). **Not green — reported as rc 3, not as pass.** ## What this does NOT do - **It is unreconciled.** Nothing here has been observed to put the server back on the account. What is proven is that the new URL answers 200 with a valid cert and that every currently advertised URL cannot complete. - **Off-LAN access is not fixed.** The advertised address is RFC1918. The WAN tcp/32400 forward remains unverified (no NAT hairpin here, so the LAN-side probe is inconclusive, not proof). - **`ManualPortMappingMode="1"` with an absent port is left as-is** — no GitOps path exists for it (the image supports no `PLEX_PREFERENCE*`), so it is an operator UI step. - **PiHole resolver behaviour untested.** `plex.direct` resolved from a pod; LAN clients resolve via PiHole, and dnsmasq's `stop-dns-rebind` would reject a public answer carrying a private address. This is the most likely way the fix still fails. - This env var will **overwrite** `customConnections` on the next pod start, deliberately discarding anything typed into the web form since. ## ⚠️ Needs `/approve-override` This touches `clusters/**` (two files), so `ci/approval-policy.sh` will **block** it. It needs `/approve-override <reason>` posted as a PR **review** of type "Comment". I have deliberately not posted it: CLAUDE.md §1 forbids an agent clearing this on its own PR, and the agent account holds `owner`, so the prohibition is a rule rather than a limit. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gitea-admin added 1 commit 2026-10-04 07:53:51 +00:00
fix(plex): advertise the plex.direct name, not the bare VIP (server vanished from the plex.tv account)
gate / gate (pull_request) Successful in 1m48s
gate / conformance (pull_request) Has been skipped
1f4552d409
customConnections held two hand-typed URLs and neither could ever complete, so
Plex had nothing publishable and the server disappeared from the account:

  - https://192.168.2.24:32400 -- the server's cert is
    *.130072f20c334d6c8f63d0dfef87309b.plex.direct with NO IP SAN, so bare-IP
    https fails TLS for every client. Measured: a continuous storm of
    "CERT: incomplete TLS handshake ... certificate unknown" from real LAN
    clients arriving through the plex-l4-tcp VIP.
  - https://plex.home.keithjackson.dev:32400 -- nothing listens; the Gateway
    serves that host on 443 (measured http=000 against 192.168.2.20:32400).

With no completable connection plex.tv published nothing ("Mapped - Not
Published"), Plex fell back to Relay, and clients reported "not authorized".

Fix: advertise <dashed-ip>.<serverhash>.plex.direct -- public DNS that resolves
to the embedded address, so it points at the LAN VIP and matches the wildcard
cert. Verified with full TLS validation (no curl -k): 200 + verify OK, where
the bare-IP form fails to connect.

Declared as ADVERTISE_IP so git owns the value instead of a web form: the
image's 40-plex-first-run writes it into customConnections on every pod start,
outside the claim guard.

Does NOT fix off-LAN access -- the advertised address is RFC1918 and the WAN
tcp/32400 forward remains unverified. ManualPortMappingMode=1 with an absent
port is left as-is (no GitOps path exists for it).

Touches clusters/** -> sensitive paths -> needs the operator's
/approve-override, deliberately not self-posted (CLAUDE.md 1).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
jakman85 approved these changes 2026-10-04 07:56:29 +00:00
jakman85 merged commit 422834d4e2 into main 2026-10-04 07:59:14 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Home-Network/home-network-k3s-impl#47