feat: updated README
This commit is contained in:
@@ -1,8 +1,124 @@
|
|||||||
# PZ8 relay
|
# pz8-relay
|
||||||
|
|
||||||
A simple utility for my iptv needs.
|
A small Go service that sits between a frequently-rotating upstream IPTV
|
||||||
Upstream links change frequently and need to be updated on devices, which is annoying.
|
provider and one or more client devices. It exposes stable URLs for the
|
||||||
This is a simple web service that will accept an HTTP request with basic authentication
|
playlist and (optionally) a merged EPG, so client devices can be configured
|
||||||
and will act as a proxy against a configured HTTP link.
|
once and forgotten.
|
||||||
|
|
||||||
The proxied URL is configured via an environment variable.
|
## What it does
|
||||||
|
|
||||||
|
- **Caches the upstream m3u_plus playlist** on a configurable interval and
|
||||||
|
serves it from a stable `/playlist` URL behind basic auth. The streams
|
||||||
|
inside the playlist are still hit directly upstream by clients — only the
|
||||||
|
playlist file is cached.
|
||||||
|
- **Optionally fetches one or more XMLTV EPG sources**, merges them, filters
|
||||||
|
channels down to those declared in the playlist, and rewrites their ids to
|
||||||
|
match the playlist's `tvg-id` values. The merged EPG is served at `/epg`
|
||||||
|
and the playlist's `#EXTM3U` header is rewritten so clients pick it up
|
||||||
|
automatically.
|
||||||
|
|
||||||
|
Channel id matching is heuristic — the normalizer collapses cosmetic
|
||||||
|
divergences (`"Italia 1"` vs `"Italia 1.it"` vs `"Italia 1 HD.it"` all map
|
||||||
|
to the same key), which is enough for most Italian providers + open-epg
|
||||||
|
sources.
|
||||||
|
|
||||||
|
For design background, see [adrs/01-epg-management.md](adrs/01-epg-management.md).
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
| Path | Auth | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `/playlist` | basic | Cached playlist; `Content-Type: application/vnd.apple.mpegurl` |
|
||||||
|
| `/` | basic | Alias for `/playlist` (back-compat) |
|
||||||
|
| `/epg` | none | Merged EPG; honours `Accept-Encoding: gzip` (~6× smaller) |
|
||||||
|
| `/healthz` | none | 200 once first refresh succeeds, else 503 |
|
||||||
|
|
||||||
|
`/epg` is intentionally unauthenticated: XMLTV client apps (TiviMate, IPTV
|
||||||
|
Smarters, etc.) auto-discover the `url-tvg` URL from the playlist header
|
||||||
|
and fetch it without reusing the playlist's credentials. The threat model
|
||||||
|
is low — an anonymous fetcher gets a few hundred KB of TV listings.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All configuration is via environment variables.
|
||||||
|
|
||||||
|
| Var | Purpose | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `PZ8_RELAY_TARGET_URL` | Upstream m3u_plus playlist URL | **required** |
|
||||||
|
| `PZ8_RELAY_USERNAME` | Basic auth username for `/playlist` | **required** |
|
||||||
|
| `PZ8_RELAY_PASSWORD` | Basic auth password for `/playlist` | **required** |
|
||||||
|
| `PZ8_RELAY_LISTEN_ADDR` | TCP address to listen on | `:8080` |
|
||||||
|
| `PZ8_RELAY_PUBLIC_URL` | Public base URL of this relay; used to rewrite `url-tvg` to `<base>/epg` | required when EPG is enabled |
|
||||||
|
| `PZ8_RELAY_EPG_URLS` | Comma-separated XMLTV URLs (gzipped or plain) | empty (EPG disabled) |
|
||||||
|
| `PZ8_RELAY_EPG_REFRESH` | Refresh interval (Go duration) | `12h` |
|
||||||
|
| `PZ8_RELAY_PREFER_PLAYLIST_EPG` | If `true` and the upstream playlist declares `url-tvg`, use that as the *only* EPG source for the cycle; otherwise fall back to `EPG_URLS` | `false` |
|
||||||
|
| `PZ8_RELAY_CACHE_DIR` | Where the cached playlist + EPG live | `/var/cache/pz8-relay` |
|
||||||
|
| `PZ8_RELAY_EPG_CONTENT_TYPE` | `Content-Type` returned by `/epg` | `application/xml` |
|
||||||
|
|
||||||
|
EPG processing is enabled when `PZ8_RELAY_EPG_URLS` is non-empty **or**
|
||||||
|
`PZ8_RELAY_PREFER_PLAYLIST_EPG=true`. With EPG disabled, the relay simply
|
||||||
|
caches and serves the upstream playlist unchanged.
|
||||||
|
|
||||||
|
## How a refresh works
|
||||||
|
|
||||||
|
A single background worker drives the refresh on the configured interval
|
||||||
|
(starting immediately on boot):
|
||||||
|
|
||||||
|
1. Download the upstream playlist, stream-rewrite the `#EXTM3U` header so
|
||||||
|
`url-tvg` points at `<PUBLIC_URL>/epg`, extract every `tvg-id`, and
|
||||||
|
atomically swap the cached `playlist.m3u` into place.
|
||||||
|
2. If EPG is enabled, fetch each configured EPG URL (transparently
|
||||||
|
gunzipping `.gz` URLs) into a per-source temp file.
|
||||||
|
3. Walk the sources twice: pass 1 emits unique `<channel>` elements (first
|
||||||
|
source wins for duplicates), pass 2 emits `<programme>` entries scoped
|
||||||
|
to the source that "owns" each channel. Channels not in the playlist's
|
||||||
|
`tvg-id` set are dropped. Channel ids in the output are rewritten to the
|
||||||
|
exact playlist form.
|
||||||
|
4. The result is gzipped on disk as `epg.xml.gz` (XMLTV compresses ~10×)
|
||||||
|
and atomically swapped into place.
|
||||||
|
|
||||||
|
If a refresh fails partway through, the previous good cache is preserved
|
||||||
|
and the next tick retries. `/playlist` and `/epg` only return 503 before
|
||||||
|
the very first successful refresh.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
Local build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go build ./...
|
||||||
|
./pz8-relay
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build -t pz8-relay .
|
||||||
|
docker run --rm -p 8080:8080 --env-file .env pz8-relay
|
||||||
|
```
|
||||||
|
|
||||||
|
The container runs as the distroless `nonroot` user (uid 65532). The
|
||||||
|
default cache directory is created in the image with the right ownership;
|
||||||
|
mount a volume on `/var/cache/pz8-relay` if you want the cache to survive
|
||||||
|
restarts.
|
||||||
|
|
||||||
|
Minimum `.env` for EPG-enabled operation:
|
||||||
|
|
||||||
|
```
|
||||||
|
PZ8_RELAY_TARGET_URL=https://upstream.example/playlist.m3u
|
||||||
|
PZ8_RELAY_USERNAME=alice
|
||||||
|
PZ8_RELAY_PASSWORD=hunter2
|
||||||
|
PZ8_RELAY_PUBLIC_URL=https://relay.example.com
|
||||||
|
PZ8_RELAY_EPG_URLS=https://www.open-epg.com/files/italy1.xml.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Covers id normalization, playlist header rewriting + tvg-id extraction,
|
||||||
|
and the EPG two-pass merge (filtering, id rewriting, first-source-wins
|
||||||
|
dedup). HTTP wiring and the refresh ticker are not unit-tested; smoke-test
|
||||||
|
manually with the endpoints above.
|
||||||
|
|||||||
Reference in New Issue
Block a user