prengineer/stalker-plexer

By prengineer

Updated 4 days ago

Access your IPTV Stalker/Ministra content with Plex!

Image
Integration & delivery
0

476

prengineer/stalker-plexer repository overview

image

Please Support This Project!

Donating is the best way to keep this project functional and to show your appreciation & support.

MethodEasy WayReceiving Account
PayPal[email protected]
BitCoin19JXFGfRUV4NedS5tBGfJhkfRrN2EQtxVo

Stalker-Plexer

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.).

About

Why does this exist?

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.

What it does

One process, one HTTP port, four jobs:

  1. Pulls the channel list from your provider's Stalker portal and exports the complete lineup as TVChannels.m3u.
  2. Pulls the EPG on a schedule (default every 4 h, because providers typically serve only a ~5-hour forward window) and exports 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.
  3. Splits the lineup into 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.
  4. Serves all of it over HTTP, plus a stream proxy (/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.

How it works (the interesting parts)

  • EPG accumulation. The provider usually ignores the 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.
  • Stable chunking. The lineup is ordered by the provider's channel number, and the same order drives both the full M3U and the chunks, so chunk 01 is always the first 480 channels and boundaries never shift between exports.
  • Channel numbers. Each channel line carries the provider's number two ways: a 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)).
  • Stream proxy. Three channel families: open edges (public FAST/CDNs) get direct URLs; edges that 403 without the MAG250 User-Agent are routed through /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.
  • Atomic exports. M3U/EPG files are written to a temp name and renamed into place — the HTTP server never serves a half-written file.
  • Non-root. The container starts as root only long enough to chown /data to PUID/PGID, then drops to that user before binding the port. Nothing is ever served as root.

Supported architectures

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 platformRuns on
linux/amd64x86_64 servers / desktops
linux/arm64Raspberry 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.

Quick start

Running through the command line:
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
Running with Docker Compose:

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.

Key setup notes
  • 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:

    SettingInstance #01Instance #02...
    M3U inputhttp://stalker:8080/TVChannels-01.m3uhttp://stalker:8080/TVChannels-02.m3u...
    EPG inputhttp://stalker:8080/epg.xmlhttp://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
    

Configuration

All configuration is environment variables (compose environment: or docker run -e).

VariableDefaultMeaning
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.
PUID1000UID the process runs as (dropped from root at startup). Match it to the owner of your data folder.
PGID1000GID of that user.
PUBLIC_HOSTlocalhostHost string embedded in M3U proxy lines. Must be resolvable from the xTeVe / Threadfin containers.
PORT8080Container-side HTTP port: files + proxy + health.
BIND0.0.0.0Interface to bind.
MODEsmartsmart = proxy only the protected channels (default) · direct = raw provider URLs · proxy = route everything through the proxy.
CHUNK_SIZE480Channels per TVChannels-NN.m3u. Plex's cap is 480 per instance — keep it.
EPG_INTERVAL_HOURS4EPG refresh cadence. Keep it below the provider's ~5-hour window so the forward guide never gaps.
EPG_PERIOD5Nominal EPG window sent to the portal (providers largely ignore it).
CHANNEL_INTERVAL_HOURS24How often to re-pull the channel list, in hours.
KEEP_DAYS3Days of real-EPG history retained in the guide.
FAKE_EPG_HOURS24Length of the generated "Unknown Programming" grid per channel without EPG, in hours.
DATA_DIR/dataWhere stalker.db + output files live (the volume).
SCHEDULER10 = serve existing files only, never pull (read-only replicas).

Ports & volume

ItemValueNotes
HTTP8080/tcpM3U + EPG + stream proxy + health, all on one port. Publish any host port.
Volume/datastalker.db, TVChannels*.m3u, epg.xml — contents are served over HTTP.

HTTP endpoints

PathWhat it is
GET /Human-readable landing page: service details, file list, endpoint reference.
GET /infoMachine-readable JSON index: service details, every served file with sizes, ready-to-paste xTeVe M3U/EPG URLs.
GET /healthzHealth: DB reachable, channel/EPG counts, last pull/export times. Used by the built-in healthcheck.
GET /TVChannels.m3uFull lineup (all channels).
GET /TVChannels-NN.m3uOne 480-channel chunk (01NN).
GET /epg.xmlShared XMLTV guide (real EPG + "Unknown Programming" grid).
GET /live/<id>.m3u8Stream 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.

One-shot commands

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)

Troubleshooting

SymptomLikely cause / fix
handshake returned no tokenWrong 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 retriesProvider throttling (it answers "not now" with empty 200s; usually clears in 30–60 s). The scheduler retries next tick.
Some channels 403 / black screenThey're the MAG250 family and MODE=direct is set. Switch to smart and re-export.
xTeVe can't reach the M3U/streamsPUBLIC_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 URLsThose channels must go through the proxy — MODE=smart handles this automatically.
Guide looks shortBy 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 changesRe-pull with the fetch subcommand, or wait for the 24-h channel schedule.
Host port busyPublish a different host port: -p 9090:9090.

⚠️ Credentials

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.

Simultaneous streams

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.

Support

If you found this image useful, a donation keeps the project alive.

Tag summary

Content type

Image

Digest

sha256:64ab5cd13

Size

9 MB

Last updated

4 days ago

docker pull prengineer/stalker-plexer