diff --git a/CLAUDE.md b/CLAUDE.md index e95f5423b..65b5d5017 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,8 +35,8 @@ Custom IPTV channel server for Jellyfin. Forked from [ErsatzTV/ErsatzTV](https:/ ## Deployment -- **Docker host**: bumblebee (192.168.1.99), container `ersatztv`, port 8409 -- **Config volume**: `~/downloadswarm/ersatztv/` on bumblebee → `/config` in container +- **Docker host**: **jazz (192.168.1.29)**, container `ersatztv`, port 8409. Media transcoders (Jellyfin, `ersatztv`, `ersatztv-test`) moved here from bumblebee on 2026-07-20 (server-management#633); bumblebee (192.168.1.99) still hosts the **CI runners** and the rest of the stacks. **Name-reuse trap**: `jazz` was an *earlier* name for the .99 host, so pre-2026-07-20 docs/commits saying "jazz" mean today's **bumblebee** — go by the IP, not the name. +- **Config volume**: `~/downloadswarm/ersatztv/` on jazz → `/config` in container - **SQLite DB**: `/config/ersatztv.sqlite3` (WAL mode, root-owned) - **Images** (our fork, built by `.gitea/workflows/docker-build.yml` → `192.168.1.95:3000/timothy/ersatztv`): push to `main` → `:latest` + `:` (test image); push `v*` tag → `:prod` + `:` + `:`. Prod's **Komodo GitOps** `media-servers` stack follows floating `:prod`; after the immutable `:` candidate passes the release scans, manually deploy the stack (Global Auto Update is the daily fallback). Both paths run the fail-closed pre-deploy backup and prod-copy migration smoke before recreation. Test tracks `:latest`. Pipeline details: `docs/ci-cd.md`. diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 37009171a..7e75e7798 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -5,6 +5,20 @@ to the **Gitea container registry**. Runner + registry were provisioned in server-management#172; the build pipeline is ersatztv#4; test/prod containers are server-management#481. +## Hosts (read this before trusting a hostname below) + +| Host | IP | Role | +|---|---|---| +| **jazz** | 192.168.1.29 | **Docker host for the media transcoders** — prod `ersatztv` (8409), `ersatztv-test` (8410), Jellyfin. Release scans (`security-scan.sh`) and the prod-copy `migration-smoke.sh` run here. | +| **bumblebee** | 192.168.1.99 | CI runners (`bumblebee-runner`, `small-runner`), plus every other Docker stack. All the memory/lane measurements below were taken here. | +| **ci-runner** | VM 127 (pve4) | The other `ubuntu-latest` CI runner. | + +The transcoders moved bumblebee → jazz on **2026-07-20** (server-management#633). + +> **Name-reuse trap.** `jazz` was an **earlier name for the .99 host**. Anything written before +> 2026-07-20 that says "jazz" means today's **bumblebee** — resolve hostnames by **IP**, not by name, +> and don't "fix" a historical bumblebee reference into jazz. + ## Versioning & releases The fork inherits upstream ErsatzTV's scheme: **`vYY..`** (lightweight, `v`-prefixed git tags). @@ -22,6 +36,8 @@ Upstream's final release was **`v26.3.0`** (archived). Our line continues from t | `v26.7.0` | Blazor-removal release: ChicoryTV became the only UI. | | `v26.8.0` | Secured/versioned ChicoryTV SPA + REST API go-live release (#335). | | `v26.9.0` | Configurable advertised IPTV base URL for M3U/XMLTV (#340) + SPA shell/routing + playouts modularization (#247/#245); on-air/Plex/library-path fixes (#99/#345/#371); coverage + functional-E2E CI (#15/#299). | +| `v26.10.0` | Auto-Tune channel workflow (#69) + weighted content distribution (#70); scheduling refactors, health-check remediation UX (#164), HLS cold-start instrumentation (#350), security hardening (#293/#376/#308). | +| `v26.11.0` | Media-scanner resilience: Jellyfin mixed-content libraries (#489) + music-video scan correctness (#488/#494/#497); remote-stream probing before ffmpeg (#473/#480); weighted-distribution SPA (#404). **First release deployed to `jazz`** (server-management#633). | **Before cutting a release — consolidate `docs/decisions.md`.** The log is append-only between releases (ersatztv#303 H9), so a release boundary is where superseded entries get pruned/merged and the Index @@ -34,7 +50,7 @@ consolidate here — or sooner if the `decisions-guard` job's 1800-line consolid 1. Confirm `main` CI is green; run the full local gate plus `dotnet list package --vulnerable --include-transitive`; then push a `vYY.N.P` tag on that exact `main` commit. 2. Wait for tag CI to build `:prod` + the immutable `:` + `:` images. Run - `scripts/security-scan.sh` on bumblebee against **the immutable `:` image**, not a + `scripts/security-scan.sh` on jazz against **the immutable `:` image**, not a moving tag, and triage every ZAP/semgrep finding. 3. Only after the candidate passes, manually `DeployStack media-servers` and observe its pre-deploy output. Prod's compose deliberately follows floating `:prod` (Timothy's 2026-07-11 decision), so @@ -645,7 +661,7 @@ running product from outside our C#/review stack. `[RequiresAuthentication]` + `RequireKeyForReads` surface (not just the `/app` shell an unauthenticated spider sees). Then a **semgrep** SAST cross-check (`p/security-audit` + `p/secrets` + `p/csharp`). The container is torn down on exit. -- **Where/when.** Runs on the **docker host** (bumblebee — the Mac has no docker), like +- **Where/when.** Runs on the **docker host** (jazz — the Mac has no docker), like `migration-smoke.sh`: `scripts/security-scan.sh [IMAGE] [PORT]` (defaults `…:latest` / `8411`). It is a **manual release-gate**, deliberately **not** a per-PR CI job — it needs docker + a booted image, takes several minutes, and is noisy (expect to tune, not take raw). The continuous layer is the per-PR @@ -879,13 +895,13 @@ entirely in `web/`), the wiring is: ## Registry Gitea Packages, HTTP-only at `192.168.1.95:3000`. the `ci-runner` VM's Docker daemon (192.168.1.127) has it as an -insecure-registry (server-management#172; runner relocated off jazz in #570). Images: `192.168.1.95:3000/timothy/ersatztv:`. +insecure-registry (server-management#172; runner relocated off bumblebee in #570). Images: `192.168.1.95:3000/timothy/ersatztv:`. ## Test / prod environments Container/compose wiring lives in **server-management** (project boundary): test `ersatztv-test` on 8410 (`:latest`), prod `ersatztv` on 8409 (`:prod`). See -server-management#481 for the full spec (registry pull on jazz, volumes, Jellyfin +server-management#481 for the full spec (registry pull on the docker host, volumes, Jellyfin isolation for test, Watchtower/manual promotion). ## Retired upstream workflows diff --git a/docs/m3u-xmltv.md b/docs/m3u-xmltv.md index e5a8560cb..7f6c91874 100644 --- a/docs/m3u-xmltv.md +++ b/docs/m3u-xmltv.md @@ -89,7 +89,7 @@ This method is called only by `WatermarkSelector.cs` (lines 219, 268, 317, 385) ### Confirmed Root Cause (2026-06-26) -Live experiment against the running `ersatztv` container on jazz (192.168.1.99:8409) proves the host in `tvg-logo` is **entirely a function of the incoming `Host` header** — there is no hardcoded `localhost` in the M3U/XMLTV path: +Live experiment against the running `ersatztv` container on bumblebee (192.168.1.99:8409) proves the host in `tvg-logo` is **entirely a function of the incoming `Host` header** — there is no hardcoded `localhost` in the M3U/XMLTV path: ``` curl -H "Host: localhost:8409" .../iptv/channels.m3u -> tvg-logo="http://localhost:8409/iptv/logos/...jpg"