Make the ChicoryTV SPA the default UI: GET/HEAD requests to / and to
legacy Blazor routes with SPA equivalents (channels, schedules,
playouts, libraries, all settings pages, channel add) now 302 to their
/app counterparts, preserving query strings and the ETV_BASE_URL path
base. 302 not 301: the map will grow as parity lands and permanent-
redirect caching would make rollback painful.
Blazor-only functionality (collections, media browse/search, trakt,
filler presets, watermarks, ffmpeg profiles, blocks/decos/templates,
playout detail editors, logs, troubleshooting, channel edit) keeps
serving Blazor; the Blazor home stays reachable at /system/health.
Parity gaps are tracked in #140-#146; Blazor removal is phase (b).
Also adds an /app smoke assertion to the docker build workflow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docker-build.yml: the image `build` job now `needs: [test, migrations]`, so a
broken migration blocks the image build (not just the test gate). main branch
protection also now requires the "EF migration integrity (SQLite + MySql)" check
on PR merges. (follow-through on #13)
- docs/handoffs/m3u-tvg-logo-fix.md: working brief for the next task (#1, M3U
tvg-logo localhost), incl. the finding that the issue's GenerateChannelLogoUrl
root-cause is a red herring (that localhost is the ffmpeg watermark path, fetched
in-container; investigate the request flow / forwarded headers / cached values
instead). References the #11 golden + #16 E2E regression nets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the image smoke test from "serves HTTP" to a real E2E: after HTTP readiness,
assert the Jellyfin-facing surfaces on the freshly built image — /iptv/channels.m3u
returns 2xx containing #EXTM3U, and /iptv/xmltv.xml returns 2xx containing a <tv root.
xmltv.xml needs channels.xml (the scheduler writes it a few seconds after boot), so each
endpoint is polled with a deadline; container logs are dumped on failure. Catches
routing / base-URL (#1) / migration regressions that leave the app "up" but serving
broken playlists/guides.
Validated the exact checks against the real :latest image on a fresh container (both 200
with the expected shape) before committing.
Refs #16
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New `migrations` job in docker-build.yml runs on every push/PR and, per provider:
- `dotnet ef migrations has-pending-model-changes` — fails on model drift (an entity
changed without a matching migration).
- `dotnet ef database update` against a fresh empty DB — applies all migrations,
catching broken/un-orderable ones.
SQLite (the prod provider) uses a throwaway file; MySql uses ServerVersion.AutoDetect
(connects at config time), so the job runs a `services: mysql:8.4` container (the
act_runner uses Docker execution on network downloadswarm, so the service is reachable
as `mysql:3306`). Independent gate for now (not a `needs:` of the image build) so the
MySql-service dependency can't block image builds until proven stable.
Validated both providers locally against real DBs: SQLite 787 migrations -> 139 tables;
MySql 8.4 305 migrations -> 137 tables; both model-drift checks clean.
Docs: ci-cd.md "Migration integrity" (dual-provider discipline, EF CLI pattern, the
non-transactional caveat) + CLAUDE.md convention.
Refs #13
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add concurrency group so the single jazz runner can't run the
push-main-then-push-tag release flow in parallel (shared :buildcache
+ smoke container would collide).
- Add pull_request trigger running the test job only (PRs had no gate);
skip the build job on PRs.
- Only push images from main or a v* tag (workflow_dispatch from other
refs now builds without publishing, instead of clobbering :latest/:prod).
- Replace the log-grep smoke check with a real HTTP readiness probe
(docker exec python3 -> http://localhost:8409/), unique container name,
and trap-based cleanup to avoid leaks on cancel.
- dotnet test now runs -c Release --no-build (was rebuilding in Debug).
- Directory.Build.props: WarningsAsErrors=NU1904 so critical NuGet
advisories block in every project, not just ones with
TreatWarningsAsErrors.
- Dockerfile copies global.json + .editorconfig too, so the image build
matches CI's SDK pin and analyzer severities.
- Remove dead .github/dependabot.yml + FUNDING.yml (upstream-pointed).
- Rewrite docs/ci-cd.md to the implemented pipeline.
Refs #4, #3, #8.
ErsatzTV logs via Serilog and never emits Kestrel's 'Now listening on'
line, so the smoke test grep never matched even though the app booted
fully in ~6s. Match ErsatzTV's actual startup logs instead ('… service
started', 'Located ffmpeg'). The image build itself was already passing.
Refs #4.
Adds .gitea/workflows/docker-build.yml: a test-gated, amd64-only Docker
build that pushes the fork's own image to the Gitea container registry
(192.168.1.95:3000). The .NET test job gates the image build; the build
job stamps INFO_VERSION from git, uses buildx registry layer caching, and
smoke-tests that the built image boots before finishing.
Tagging:
- push to main -> :latest + :<short-sha> (test image; prod untouched)
- push tag v* -> :prod + :<version> + :<short-sha> (prod release)
Updates docker/Dockerfile to pull the FFmpeg base image from our Gitea
registry (192.168.1.95:3000/timothy/ersatztv-ffmpeg:7.1.1) instead of the
archived upstream ghcr.io image.
Removes the upstream .github/workflows/* — they target GHCR/DockerHub and
Azure/Apple signing secrets we don't have, call reusable workflows at dead
ersatztv/ersatztv@main paths, and were running as noise (incl. a daily
stale-issue cron) on the Gitea runner. Upstream is archived, so there are
no future merges to preserve them for.
Refs #4, #3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>