Access your IPTV Stalker/Ministra content with Plex!
476

Donating is the best way to keep this project functional and to show your appreciation & support.
| Method | Easy Way | Receiving Account |
|---|---|---|
| PayPal | ![]() | [email protected] |
| BitCoin | ![]() | 19JXFGfRUV4NedS5tBGfJhkfRrN2EQtxVo |
Stalker / Ministra IPTV → M3U + XMLTV for Threadfin / xTeVe / Plex / Jellyfin
A single Docker container that turns the Legacy Stalker Portal (the platform used by Informir MAG 250 set-top boxes) into clean M3U playlists and an XMLTV guide to feed Threadfin / xTeVe and on to Plex or Jellyfin, or straight into any player that reads M3U (MPC, VLC, Kodi, etc.).
Many IPTV providers on the Stalker/Ministra platform authenticate with the MAC address of your authorized device and no longer hand out M3U playlists or XMLTV guides. That locks their subscribers out of Plex, Jellyfin, and every other non-Stalker player.
This container sits in the middle: it speaks the Stalker protocol (as a MAG 250 device would), pulls your full channel lineup and EPG, and serves everything as standard M3U + XMLTV over a single HTTP port. No more MAG box required for the lineup and guide.
One process, one HTTP port, four jobs:
TVChannels.m3u.epg.xml. Channels without provider EPG get a generated
"Unknown Programming" grid, so every channel maps into the guide and Threadfin/xTeVe's
automatic channel↔EPG matching works for the whole lineup.TVChannels-01.m3u, TVChannels-02.m3u, … — at most
480 channels per file (Plex's per-Threadfin-instance limit). A 3,460-channel lineup
produces 8 chunks; point one Threadfin/xTeVe instance at each chunk and the whole lineup is
available in Plex./live/<id>.m3u8) for the channels
whose provider edge 403s unless the User-Agent contains MAG250.Jellyfin can consume the master M3U + EPG directly and skip Threadfin/xTeVe entirely.
period parameter and always serves one fixed
~5-hour forward window. Refreshing every 4 hours means the windows overlap; the SQLite store
accumulates them, so the guide always holds ~3 days of real history plus a ~5-hour forward
window. There is no provider EPG history API — accumulation in stalker.db is the history.tvg-number
attribute (Kodi & co.) and a (N) suffix on the display name (what xTeVe/Threadfin — and
therefore Plex — show, e.g. CNN International (1))./live/<id>.m3u8, where the proxy
re-resolves the provider URL with the right User-Agent on every request (the signed chain
contains short-lived JWTs — never cache it) and absolutizes the relative variant/key URIs.
In MODE=smart (default) only the protected family goes through the proxy./data to PUID/PGID,
then drops to that user before binding the port. Nothing is ever served as root.The app is written in Go and compiles to one static binary; the runtime image (Alpine 3.20) carries only that binary plus ca-certificates — no interpreter, no source, no build toolchain.
| Docker platform | Runs on |
|---|---|
linux/amd64 | x86_64 servers / desktops |
linux/arm64 | Raspberry Pi 4/5 (64-bit OS) and macOS on Apple Silicon (M1/M2/…) |
Built with buildx as a multi-arch manifest — docker pull automatically picks the right
architecture for your machine.
docker run -d --name stalker-plexer --restart unless-stopped \
-p 8888:8080 \
-e PUID="1000" \
-e PGID="1000" \
-e STALKER_MAC="00:1A:XX:XX:XX:XX" \
-e STALKER_BASE="http://sub.domain.tld" \
-e NEEDS_UA_HOSTS="ln.sub.domain.tld,link.sub.domain.tld" \
-e PUBLIC_HOST="stalker" \
-v stalker-data:/data \
prengineer/stalker-plexer:latest
NOTE: Anything surrounded by "<" and ">" needs to be replaced.
# Example compose stack: one "stalker-plexer" service (this container) feeding
# several Threadfin/xTeVe instances, each with its own 480-channel slice.
#
# All services share compose's default network, so the xTeVe containers
# reach the stalker-plexer service by the DNS name "stalker". That is why
# PUBLIC_HOST below is "stalker" — it is the host string baked into the
# M3U proxy lines that xTeVe must be able to resolve.
#
# For each xTeVe/Threadfin instance, in its UI/config:
# M3U input : http://stalker:8888/TVChannels-<number>.m3u (its assigned chunk)
# EPG input : http://stalker:8888/epg.xml (shared by all)
# Mapping is automatic (tvg-id == guide channel id).
services:
# Container #1 - Stalker-Plexer
stalker:
image: prengineer/stalker-plexer:latest
container_name: Stalker-Plexer
environment:
PUID: "1000" # The User used to run the container
PGID: "1000" # The Group of the User to run the container
#------------------------------------------------------------------------------------
# Your Subscription MAC Address - Required for your account [Keep it Secret!]
# All MAG 250 devices have a MAC Address in the format: 00:1A:XX:XX:XX:XX
# but your provider may allow you to use any MAC
#------------------------------------------------------------------------------------
STALKER_MAC: "00:1A:<XX>:<XX>:<XX>:<XX>"
#------------------------------------------------------------------------------------
# The base domain of your IPTV provider. Usually provided to you in this format:
# http://<sub.domain.tld>/c/index.html
#------------------------------------------------------------------------------------
STALKER_BASE: "http://<sub.domain.tld>"
#------------------------------------------------------------------------------------
# Stream hosts that error with a 403 unless the request comes from a MAG250
# (comma-separated); only these go through the proxy in MODE=smart
#------------------------------------------------------------------------------------
NEEDS_UA_HOSTS: "ln.<domain.tld>,link.<domain.tld>"
#------------------------------------------------------------------------------------
# Host name or IP address that the xTeVe / Threadfin containers use to reach THIS
# container. Must be resolvable from them - the compose service name works.
#------------------------------------------------------------------------------------
PUBLIC_HOST: "stalker"
#------------------------------------------------------------------------------------
# Tunables variables (all optional)
#------------------------------------------------------------------------------------
MODE: "smart" # Values: [ smart | direct | proxy ]
CHUNK_SIZE: "480" # Amount of channels per chunk file (TVChannels-<Number>.m3u)
EPG_INTERVAL_HOURS: "4" # How often to refresh the EPG (keep < provider's typical ~5h window)
CHANNEL_INTERVAL_HOURS: "24" # How often to scan for channel changes, in hours
KEEP_DAYS: "3" # Real-EPG history retained
FAKE_EPG_HOURS: "1" # "Unknown Programming" grid length, in hours
ports:
- "8888:8080" # Port to access the HTTP endpoint for UI and data
volumes:
- /containers/stalker:/data # Docker volume or bind mount to store the data
restart: unless-stopped
#------------------------------------------------------------------------------------
# xTeVe / Threadfin instances (one per 480-channel chunk)
# Each instance points at a DIFFERENT chunk but the SAME epg.xml
#------------------------------------------------------------------------------------
# Container #2 - xTeVe (01)
xteve-01: # -> TVChannels-01.m3u (first 480 channels in lineup order)
image: dnsforge/xteve:latest
container_name: xTeVe-01
environment:
- TZ=America/New_York
ports:
- "34400:34400" # xTeVe UI/API (for Plex)
volumes:
- /containers/xteve/01:/data # Docker volume to store the data
restart: unless-stopped
# Container #3 - Threadfin (02)
threadfin-02: # -> TVChannels-02.m3u (next 480 channels in lineup order)
image: fyb3roptik/threadfin:latest
container_name: Threadfin-02
environment:
- TZ=America/New_York
ports:
- 34401:34400 # Threadfin UI/API (for Plex)
volumes:
- "/containers/threadfin/02:/home/threadfin/conf"
restart: unless-stopped
The first pull (channels + EPG + export) completes within ~60 s of startup — watch it with
docker logs -f stalker-plexer.
STALKER_MAC — the MAC address your provider whitelisted for your account. It is your
account credential: treat it like a password.
STALKER_BASE — your portal base URL (the http://sub.domain.tld part of
http://sub.domain.tld/c/index.html).
NEEDS_UA_HOSTS — comma-separated stream hosts that errors unless the request from from a
MAG250. Leave empty only if your provider has no such hosts (then the proxy is unused).
PUBLIC_HOST — the host name your xTeVe / Threadfin containers use to reach this
container. It is embedded in the M3U proxy lines, so it must resolve from them — the compose
service name (stalker) works when they share the compose network; otherwise use a LAN IP or
DNS name they can resolve. This is the one variable that is easy to get wrong.
Ports — the container listens on 8080 (changeable via PORT); publish whatever host
port you like (e.g. -p 8888:8080). Sibling containers reach it on the container port. Exposing on
the same port avoids confusion with the links displayed in the UI. Otherwise, you will have to change to the
exposed port instead of the one listed in the UI.
Volume — /data (in container) holds stalker.db, TVChannels*.m3u, and epg.xml. The container
chowns it to PUID/PGID at startup, so bind mounts work with whatever user your host uses.
Multiple Threadfin / xTeVe instances — each instance points at its own chunk but the same shared guide:
| Setting | Instance #01 | Instance #02 | ... |
|---|---|---|---|
| M3U input | http://stalker:8080/TVChannels-01.m3u | http://stalker:8080/TVChannels-02.m3u | ... |
| EPG input | http://stalker:8080/epg.xml | http://stalker:8080/epg.xml | ... |
Channel–EPG mapping is automatic: every M3U line's tvg-id matches the <channel id=…> in
epg.xml (the provider's xmltv_id, falling back to the channel id where the provider sends
a literal "NULL").
In Plex, add each Threadfin instance as a live-TV tuner source. The 480-channel cap per instance is Plex's — which is exactly what the chunks enforce.
┌────────────────────────────────────────────┐
IPTV │ stalker (this container) │
Provider │ ┌──────────┐ ┌────────────────────────┐ │
Stalker ──┼─>│ scheduler│ │ HTTP :8080 │ │
portal │ │ 4h EPG │ │ /TVChannels-01.m3u … │ │
│ │ 24h chnl │ │ /TVChannels.m3u │ │
│ └──────────┘ │ /epg.xml │ │
│ │ │ /live/<id>.m3u8 (proxy)│ │
│ ▼ └───────────┬────────────┘ │
│ /data stalker.db │ │
│ TVChannels*.m3u │ │
│ epg.xml │ │
└─────────────────────────────┼──────────────┘
│
┌─────────────────────────┼────────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Threadfin / │ │ Threadfin / │ │ Threadfin / │
│ xTeVe #01 │ │ xTeVe #02 │ │ xTeVe #03+ │
│ 480 ch │ │ 480 ch │ │ 480 ch │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
└─────────────────────────┼─────────────────────────┘
▼
Plex
In Jellyfin, you don't need xTeVe / Threadfin, simply add the full TVChannels.m3u and epg.xml URLs.
┌────────────────────────────────────────────┐
IPTV │ stalker (this container) │
Provider │ ┌──────────┐ ┌────────────────────────┐ │
Stalker ──┼─>│ scheduler│ │ HTTP :8080 │ │
portal │ │ 4h EPG │ │ /TVChannels-01.m3u … │ │
│ │ 24h chnl │ │ /TVChannels.m3u │ │
│ └──────────┘ │ /epg.xml │ │
│ │ │ /live/<id>.m3u8 (proxy)│ │
│ ▼ └───────────┬────────────┘ │
│ /data stalker.db │ │
│ TVChannels*.m3u │ │
│ epg.xml │ │
└─────────────────────────────┼──────────────┘
│
▼
Jellyfin
All configuration is environment variables (compose environment: or docker run -e).
| Variable | Default | Meaning |
|---|---|---|
STALKER_MAC | (required) | Your whitelisted MAC. Your account credential — keep it secret. |
STALKER_BASE | (required) | Portal base URL (from http://sub.domain.tld/c/index.html). |
NEEDS_UA_HOSTS | (empty = nothing proxied) | Comma-separated stream hosts that 403 without the MAG250 User-Agent; in MODE=smart only these go through the /live proxy. |
PUID | 1000 | UID the process runs as (dropped from root at startup). Match it to the owner of your data folder. |
PGID | 1000 | GID of that user. |
PUBLIC_HOST | localhost | Host string embedded in M3U proxy lines. Must be resolvable from the xTeVe / Threadfin containers. |
PORT | 8080 | Container-side HTTP port: files + proxy + health. |
BIND | 0.0.0.0 | Interface to bind. |
MODE | smart | smart = proxy only the protected channels (default) · direct = raw provider URLs · proxy = route everything through the proxy. |
CHUNK_SIZE | 480 | Channels per TVChannels-NN.m3u. Plex's cap is 480 per instance — keep it. |
EPG_INTERVAL_HOURS | 4 | EPG refresh cadence. Keep it below the provider's ~5-hour window so the forward guide never gaps. |
EPG_PERIOD | 5 | Nominal EPG window sent to the portal (providers largely ignore it). |
CHANNEL_INTERVAL_HOURS | 24 | How often to re-pull the channel list, in hours. |
KEEP_DAYS | 3 | Days of real-EPG history retained in the guide. |
FAKE_EPG_HOURS | 24 | Length of the generated "Unknown Programming" grid per channel without EPG, in hours. |
DATA_DIR | /data | Where stalker.db + output files live (the volume). |
SCHEDULER | 1 | 0 = serve existing files only, never pull (read-only replicas). |
| Item | Value | Notes |
|---|---|---|
| HTTP | 8080/tcp | M3U + EPG + stream proxy + health, all on one port. Publish any host port. |
| Volume | /data | stalker.db, TVChannels*.m3u, epg.xml — contents are served over HTTP. |
| Path | What it is |
|---|---|
GET / | Human-readable landing page: service details, file list, endpoint reference. |
GET /info | Machine-readable JSON index: service details, every served file with sizes, ready-to-paste xTeVe M3U/EPG URLs. |
GET /healthz | Health: DB reachable, channel/EPG counts, last pull/export times. Used by the built-in healthcheck. |
GET /TVChannels.m3u | Full lineup (all channels). |
GET /TVChannels-NN.m3u | One 480-channel chunk (01…NN). |
GET /epg.xml | Shared XMLTV guide (real EPG + "Unknown Programming" grid). |
GET /live/<id>.m3u8 | Stream proxy for MAG250-protected channels (referenced by the M3U, not by you). |
Healthcheck (built in): GET /healthz every 30 s, 5 s timeout, 90 s start period, 3 retries.
The image's entrypoint accepts subcommands for debugging from a host shell:
docker run --rm -e STALKER_MAC=… -e STALKER_BASE=… prengineer/stalker-plexer:latest fetch # full pull + export, then exit
docker run --rm -e STALKER_MAC=… -e STALKER_BASE=… prengineer/stalker-plexer:latest poll # EPG refresh + export, then exit
docker run --rm -v stalker-data:/data prengineer/stalker-plexer:latest export # re-export from the DB, no network
docker run --rm prengineer/stalker-plexer:latest selftest # offline validation of writers + HTTP routes (no credentials needed)
| Symptom | Likely cause / fix |
|---|---|
handshake returned no token | Wrong STALKER_MAC/STALKER_BASE, or the account is suspended. |
WARNING: profile status=… (not 0) | Subscription inactive — data may still be served, but expect failures. |
ERROR: request failed … empty body after retries | Provider throttling (it answers "not now" with empty 200s; usually clears in 30–60 s). The scheduler retries next tick. |
| Some channels 403 / black screen | They're the MAG250 family and MODE=direct is set. Switch to smart and re-export. |
| xTeVe can't reach the M3U/streams | PUBLIC_HOST isn't resolvable from the xTeVe / Threadfin containers, or the ports aren't published. Test from inside an xTeVe / Threadfin container: wget -qO- http://<PUBLIC_HOST>:8080. |
| xTeVe 404s on variant/key URLs | Those channels must go through the proxy — MODE=smart handles this automatically. |
| Guide looks short | By design — it's the accumulated ~5-h forward windows plus KEEP_DAYS history; it grows while the container runs. Delete /data/stalker.db for a clean slate. |
| Playlist stale after provider changes | Re-pull with the fetch subcommand, or wait for the 24-h channel schedule. |
| Host port busy | Publish a different host port: -p 9090:9090. |
STALKER_MAC and the stream paths inside the M3U files are your provider credentials.
Anyone with stalker.db or the TVChannels*.m3u files can play your account. Keep the /data
volume private — never commit it or the data directory to a public repository.
Most providers allow only one channel at a time per account; multi-stream packages are becoming more common. If your provider is single-stream, only one Plex / Jellyfin device can stream at a time, even though the lineup is available to all of them. This image does not bypass provider stream limits — don't try to use it to do so. You'll just waste your time.
If you found this image useful, a donation keeps the project alive.
Content type
Image
Digest
sha256:64ab5cd13…
Size
9 MB
Last updated
4 days ago
docker pull prengineer/stalker-plexer