ci: gate image build on migrations + #1 handoff #33
@@ -131,7 +131,7 @@ jobs:
|
||||
build:
|
||||
name: Build & push image (amd64)
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
needs: [test, migrations]
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Handoff — ersatztv#1: M3U `tvg-logo` (and channel-logo) URLs hardcoding localhost
|
||||
|
||||
**How to use:** start a fresh session and say something like
|
||||
*"Read `docs/handoffs/m3u-tvg-logo-fix.md` and execute ersatztv#1"* (optionally *"with ultracode"* /
|
||||
*"use a workflow"*). Read **ersatztv#1** in full (Gitea, `timothy/ersatztv`) before acting.
|
||||
|
||||
This is the first **feature/bug** task after the engineering-foundations batch (all 8 done: CPM, vuln
|
||||
deps, analyzers, arch tests, golden tests, migration checks, IPTV E2E, contributors guide). #1 is
|
||||
`priority: high` (Jellyfin channel logos break after every guide refresh) and was deliberately
|
||||
**unblocked + protected** by that work.
|
||||
|
||||
## ⚠️ Investigation first — the issue's stated root cause is partly a RED HERRING
|
||||
|
||||
A quick trace (do not skip — it'll save you from the wrong rabbit hole):
|
||||
|
||||
- `ChannelLogoGenerator.GenerateChannelLogoUrl()` (`ErsatzTV.Core/Images/ChannelLogoGenerator.cs:81`)
|
||||
**does** hardcode `http://localhost:{StreamingPort}…`. **But its only callers are in
|
||||
`ErsatzTV.Core/FFmpeg/WatermarkSelector.cs`** (4×) — i.e. it's the URL **ffmpeg** fetches to draw the
|
||||
channel logo as a *watermark*, and ffmpeg runs **inside the ErsatzTV container**, where
|
||||
`localhost:8409` correctly reaches ErsatzTV. So this `localhost` is **not** the M3U bug — leave it
|
||||
(or confirm before touching).
|
||||
- The **M3U** `tvg-logo` is built in `ChannelPlaylist.ToM3U()`
|
||||
(`ErsatzTV.Core/Iptv/ChannelPlaylist.cs:65,70`) from `{_scheme}://{_host}{_baseUrl}` — i.e. the
|
||||
**request** host. That's already "correct" given a correct request host.
|
||||
- The **XMLTV** guide (`GetChannelGuideHandler`) and artwork (`RefreshChannelDataHandler:~1114`) use a
|
||||
**`{RequestBase}` placeholder** substituted at request time with `{Scheme}://{Host}{BaseUrl}` — also
|
||||
request-host based.
|
||||
- `Startup` **does** configure `ForwardedHeaders.All` (`Startup.cs:126`) + `app.UseForwardedHeaders()`
|
||||
(`:534`), and `ETV_BASE_URL` → `UsePathBase` (`:505`).
|
||||
|
||||
**So: where does `localhost` actually reach Jellyfin?** Most likely one of:
|
||||
1. **Request flow / proxy**: Jellyfin (or a reverse proxy) fetches the M3U via a host that resolves to
|
||||
`localhost` from ErsatzTV's perspective, **or** `UseForwardedHeaders()` runs too late / the proxy
|
||||
isn't sending `X-Forwarded-Host`, so `Request.Host` ends up `localhost:8409`. Check middleware
|
||||
**ordering** (forwarded-headers must run before anything reading `Request.Host`) and what host
|
||||
ErsatzTV actually sees.
|
||||
2. **A cached value**: `channels.xml` / per-channel fragments are generated by `RefreshChannelList`/
|
||||
`RefreshChannelData` (background, **no** request) — confirm every host-bearing field there is a
|
||||
`{RequestBase}` placeholder, not a baked absolute URL. A baked `localhost` there would leak into the
|
||||
guide regardless of the request.
|
||||
3. **Genuine misconfiguration** (the issue flags this): the Jellyfin-side M3U URL points at `localhost`.
|
||||
|
||||
**Reproduce first.** You can run the real image on bumblebee (Docker host, `ssh timothy@192.168.1.99`,
|
||||
registry `192.168.1.95:3000/timothy/ersatztv:latest`) and curl `/iptv/channels.m3u` +
|
||||
`/iptv/xmltv.xml` with different `Host`/`X-Forwarded-*` headers to see exactly when `localhost`
|
||||
appears. (This is how #16's E2E was validated.)
|
||||
|
||||
## Likely fix shapes (decide after repro)
|
||||
- If it's forwarded-headers ordering/config → fix middleware order / `KnownProxies`/`KnownNetworks`,
|
||||
or honor an explicit configured public base URL.
|
||||
- If a background-generated cache bakes a host → switch it to the `{RequestBase}` placeholder pattern
|
||||
(copy `GetChannelGuideHandler.cs:51` / `RefreshChannelDataHandler.cs:~1114`).
|
||||
- If purely Jellyfin/proxy config → document it (and this may belong in server-management, not here).
|
||||
|
||||
## Regression net (already in place — use it)
|
||||
- **M3U golden tests**: `ErsatzTV.Core.Tests/Iptv/ChannelPlaylistGoldenTests.cs`. If your fix changes
|
||||
`ToM3U` output, the goldens **will** fail — review the diff, then regenerate intentionally with
|
||||
`ETV_UPDATE_GOLDENS=1` (or the `[Explicit] Regenerate_goldens` test) and commit. (ersatztv#11)
|
||||
- **Container E2E**: the smoke step asserts `/iptv/channels.m3u`→`#EXTM3U` and `/iptv/xmltv.xml`→`<tv`
|
||||
on the built image (main/tag builds). (ersatztv#16)
|
||||
- The **XMLTV icon URLs are not golden-tested yet** (that's ersatztv#28). If #1 touches the guide's
|
||||
logo/icon URLs, consider adding an XMLTV golden as part of this work (or at least a focused test),
|
||||
since the M3U goldens won't cover it.
|
||||
|
||||
## Process (non-negotiable — see CLAUDE.md + `docs/contributing.md`)
|
||||
- **Root cause**: document WHY (this is a bug fix). If it's config not code, say so and close with that.
|
||||
- Follow established patterns (`docs/contributing.md`); diverge only with a stated reason.
|
||||
- **One branch = one PR.** PR runs `test` + `migrations` (both now **required** to merge); merge to
|
||||
`main` runs `test`+`migrations`+`build`+smoke/E2E. Verify green before closing.
|
||||
- Adversarial self-review of the diff before closing. Task Completion Protocol / `/done 1`.
|
||||
- CI poll: `curl -u timothy:ded89Lm4 …/api/v1/repos/timothy/ersatztv/actions/tasks` (jobs by name).
|
||||
|
||||
## Repo state at handoff
|
||||
- All foundations merged to `main`; `main` requires `Build & test (.NET)` **and** `EF migration
|
||||
integrity (SQLite + MySql)` to merge a PR. Image `build` now `needs: [test, migrations]`.
|
||||
- Versioning is CalVer `vYY.N.P`; a bug-fix release for #1 would be `v26.4.0` (first release with app
|
||||
changes) — but only tag when the user asks.
|
||||
- `ssh-mcp`/`docker-mcp` target `192.168.1.99` (bumblebee); if they fail "auth", their `.mcp.json`
|
||||
needs `--key=~/.ssh/id_rsa` + a client restart. Plain Bash `ssh timothy@192.168.1.99` works.
|
||||
Reference in New Issue
Block a user