Bridges an Xtream Codes IPTV provider with Sonarr and Radarr. Search your IPTV catalog through the standard *arr interface, "download" content as .strm files, and play directly in Jellyfin — no video is ever downloaded to disk.
- Sync — VODarr fetches your full IPTV catalog from the Xtream provider, enriches each title with IMDB/TVDB IDs via TMDB, and holds everything in memory. The index is cached to disk so restarts are instant.
- Search — Sonarr/Radarr query VODarr like any Newznab indexer (port 9091). Searches match by IMDB ID, TVDB ID, or fuzzy title.
- Grab — Sonarr/Radarr send a "download" request to VODarr's fake qBittorrent client (port 9092). VODarr writes a tiny
.strmfile pointing to the stream URL and immediately reports the torrent as complete. - Play — Jellyfin imports the
.strmfile and streams directly from the IPTV provider on playback.
cp config.example.yml config.yml
# Edit config.yml with your Xtream and TMDB credentials
docker compose up -dOpen http://<host>:9090 for the web UI.
Add your arr instances to config.yml under the arr: section (see Configuration), then open the VODarr web UI → Settings → Arr Instances and click Setup next to each instance.
VODarr will automatically:
- Add itself as a Torznab indexer (categories 5000 for TV, 2000 for movies)
- Add itself as a qBittorrent download client
- Register a webhook so arr notifies VODarr on import (used for cleanup)
The Settings page shows the live connection and webhook status for each configured instance.
Indexer (Torznab):
| Setting | Value |
|---|---|
| URL | http://<vodarr-host>:9091 |
| Movie categories | 2000 |
| TV categories | 5000 |
Download client (qBittorrent):
| Setting | Value |
|---|---|
| Host | <vodarr-host> |
| Port | 9092 |
Webhook (optional but recommended):
In Sonarr/Radarr → Settings → Connect → add a Webhook pointing to http://<vodarr-host>:9090/api/webhook. This lets VODarr remove the .mkv stub files from disk after arr imports the episode, keeping your output directory clean.
VODarr writes .strm files to the configured output.path. Sonarr, Radarr, and Jellyfin all need read access to that same directory. When running via Docker, mount the same volume path in all containers.
Every .strm file contains your Xtream username and password in the stream URL:
http://provider.example.com/movie/username/password/12345.mkv
This is a limitation of the Xtream Codes protocol — credentials are embedded in stream URLs by design and cannot be removed.
What this means in practice:
- Anyone with read access to your
.strmoutput directory can see your Xtream credentials. - Media players (Jellyfin, Plex, Emby) and library scanners will have access to these files in the normal course of operation.
- If your media server logs playback errors, the full URL (including credentials) may appear in logs.
Recommended mitigations:
- Restrict the output directory to the minimum set of users/processes that need it (
chmod 750). - Do not expose the output directory publicly or via an unauthenticated network share.
- If your IPTV provider supports sub-accounts or connection limits, create a dedicated account for VODarr with a unique password. That way, if credentials are ever exposed, you can rotate just that account without affecting other devices.
- Enable VODarr web UI authentication (
server.web_username/server.web_password) if the web port is accessible on your network.
xtream:
url: "http://provider.example.com" # Base URL, no trailing slash
username: "user"
password: "pass"
tmdb:
api_key: "your-tmdb-api-key" # Free at themoviedb.org/settings/api
# tvdb_api_key: "your-tvdb-api-key" # Optional; enables TVDB fallback for unmatched series
output:
path: "/data/strm" # Root for all .strm files
movies_dir: "movies" # → /data/strm/movies/
series_dir: "tv" # → /data/strm/tv/
sync:
interval: "6h" # Go duration: 1h, 6h, 24h, etc.
on_startup: true
parallelism: 10 # Concurrent workers for TMDB enrichment (1–20)
grace_cycles: 3 # Syncs to retain items missing from provider before removing (0 = immediate)
# title_cleanup_patterns: # Regex patterns stripped from stream names before TMDB search
# - '\s*\(DUBBED\)'
# - '\s*\[EXTENDED\]'
server:
newznab_port: 9091
qbit_port: 9092
web_port: 9090
# Optional auth
web_username: ""
web_password: ""
api_key: "" # Newznab API key; empty = no auth
qbit_username: "" # qBittorrent auth (if required by arr)
qbit_password: ""
external_url: "" # Public base URL (e.g. http://vodarr:9090); used in webhook URLs sent to arr
logging:
level: "info" # debug | info | warn | error
# arr instances for auto-configure (Settings → Arr Instances → Setup)
# arr:
# instances:
# - name: "Sonarr"
# type: sonarr # "sonarr" or "radarr"
# url: "http://<host>:8989"
# api_key: "<api-key>"
# - name: "Radarr"
# type: radarr
# url: "http://<host>:7878"
# api_key: "<api-key>"The web UI dashboard shows live sync statistics including total items, unenriched count, grace-retained items, last expired items, and the duration of the most recent sync. A Sync History table lists the last 20 sync runs with per-run counts, refreshed automatically every 5 seconds.
When a title disappears from the provider catalog (e.g. during a temporary outage), VODarr retains it in the index for a configurable number of syncs before removing its .strm files. This prevents Sonarr/Radarr from losing track of content during short-lived outages.
Configure via sync.grace_cycles (default: 3). Set to 0 to remove items immediately on the next sync that doesn't return them.
If you change your Xtream password, all existing .strm files will point to the old credentials. After updating the config, trigger a full rewrite without running a new sync:
POST /api/strm/refresh
This rewrites every .strm file on disk with the current credentials from the config.
See docs/intent.md for a detailed breakdown of the architecture, data flow, and the reasoning behind key design choices.