Self-hosted Stremio addon — query your Jackett and stream torrents instantly via an embedded BitTorrent client. Static Go binary, ~20 MB Docker image, no debrid, no qBittorrent, no volumes.
- On your homelab/server:
docker compose up -d
- From any device on the same LAN, open
http://<server-ip>:7000/configure. - Fill in your Jackett URL + API key, your TMDB API key. For Addon public URL just enter your server's LAN IP (e.g.
192.168.0.15) — the page auto-converts it to an HTTPS URL (https://192-168-0-15.local-ip.medicmobile.org:7001) so Stremio clients on other devices can reach it with a valid TLS cert. - Click Install in Stremio — the native Stremio app opens and installs the addon.
- Pick a movie or episode — Jackett torrents appear as sources.
No domain, no cert management, no per-device trust setup. HTTPS uses a wildcard cert from local-ip.medicmobile.org (see HTTPS details for how it works and its tradeoffs).
Stremio app
│ GET /{config}/manifest.json
│ GET /{config}/stream/{type}/{id}.json
▼
[Go net/http + Stremio addon]
│
├─ Resolve IMDB id → titles via TMDB (24 h cache)
├─ Search Jackett in parallel across title variants
├─ Parse + sort torrents (quality > source > hdr > seeders)
└─ Return streams pointing back to /stream/{config}/{torrentId}/{fileIdx}
│ GET /stream/:config/:torrentId/:fileIdx
▼
[anacrolix/torrent singleton]
│
├─ Download .torrent via Jackett (with passkeys)
├─ Reader-driven piece prioritization (responsive + 16 MB readahead) —
│ only the parts you actually watch are downloaded
├─ Wait for torrent metadata, then serve via http.ServeContent (Range-aware)
└─ Clean up idle torrents + their on-disk pieces, respect maxConcurrentTorrents
- 🪶 Static Go binary — ~20 MB Docker image on
scratchbase, <50 ms startup, ~30 MB RSS - 🧲 Direct streaming from Jackett results, no debrid, no qBittorrent
- 🔒 Private-tracker friendly: uses Jackett's proxied
.torrentURLs with passkeys; DHT / LSD / µTP disabled - 🔐 HTTPS out of the box via a self-refreshing wildcard cert — no reverse proxy, no rebuilds when it rotates
- ⚙️ Per-user credentials encoded in the addon URL (zero server-side storage)
- 💾 Client-side config persistence (localStorage) + bookmarkable
/<config>/configureURL - 🎬 Movies + TV series (anime covered via category
5070) - 🌍 TMDB-powered title resolution with localized + original-title variants for better search coverage
- 🏷️ Quality / source / HDR / codec / audio detection
- 🎚️ Advanced filters: preferred language, quality floor/ceiling, size bounds, blacklist keywords
- 📦 Single Docker container, no volumes, multi-arch (amd64 + arm64)
- A running Jackett instance reachable from this container.
- A free TMDB API key → https://www.themoviedb.org/settings/api.
- Docker + Docker Compose on the host.
docker run -d --name jackstream \
-p 7000:7000 -p 7001:7001 \
--restart unless-stopped \
ghcr.io/lborruto/jackstream:latestservices:
jackstream:
image: ghcr.io/lborruto/jackstream:latest
container_name: jackstream
ports:
- "7000:7000" # HTTP (configure page + localhost Stremio)
- "7001:7001" # HTTPS (for Stremio clients on other LAN devices)
restart: unless-stoppedgit clone https://github.com/lborruto/jackstream.git
cd jackstream
go build -o jackstream ./cmd/jackstream
./jackstreamRequires Go 1.24+. All deps are stdlib except github.com/anacrolix/torrent (fetched automatically on build).
- Open
http://<server-ip>:7000/configurefrom any device on your LAN. - Fill in:
- Jackett URL (e.g.
http://<server-ip>:9117) and its API key. - TMDB API key.
- Addon public URL / LAN IP — just type your server's LAN IP (e.g.
192.168.0.15). The page auto-converts it to a valid HTTPS URL (see HTTPS details).
- Jackett URL (e.g.
- Click Test Jackett / Test TMDB to verify connectivity (live ping of both).
- (Optional) expand Advanced filters to set language preference, quality bounds, size limits, or blacklist keywords.
- Click Install in Stremio — the native Stremio app opens and installs.
- The HTTPS URL is also shown so you can paste it into Stremio manually on any device (useful for TVs without a browser).
Your inputs are saved to localStorage so reloading /configure re-populates the form. You can also bookmark http://<server-ip>:7000/<base64>/configure to carry the full config between browsers.
All optional. Defaults produce the same behavior as earlier versions.
| Setting | Effect |
|---|---|
| Preferred audio language | Sort boost for matching audio (FRENCH / MULTI / VOSTFR / ENG). FRENCH also boosts MULTI releases; same for ENG. Does not hide non-matching results. |
| Min / Max quality | Hard filter. Drops results above/below the chosen tier. |
| Min size (MB) | Drops "fake" low-size releases. |
| Max size (GB) | Drops oversized releases (useful for slow connections / small disks). |
| Blacklist keywords | Comma-separated list (case-insensitive, word-boundary). Drops results whose title matches any keyword — e.g. CAM, HDCAM, TELESYNC. |
All filters are stored in the base64-encoded addon URL, so different Stremio installs can use different filter profiles against the same backend.
jackstream uses the wildcard TLS cert from https://local-ip.medicmobile.org — a community service that issues a public *.local-ip.medicmobile.org Let's Encrypt certificate and operates a DNS server where <dashed-ip>.local-ip.medicmobile.org resolves to <ip>. This gives you valid HTTPS for any LAN IP with zero setup.
The cert is refreshed automatically at runtime: on startup and every 12 h the addon re-fetches the current cert + key from the service and hot-swaps it into the running HTTPS listener — no restart, no image rebuild when Let's Encrypt rotates it (~every 60 days). An embedded copy is baked into the binary as a fallback so HTTPS still comes up when the service is briefly unreachable.
The HTTPS listener binds on port 7001 alongside the HTTP listener on 7000. Stremio clients hit the HTTPS URL; the TCP connection still goes directly to your LAN host.
- The TLS private key is public (by design — anyone can download it from the service). Anyone who can also control DNS on your network could MITM; on a trusted home LAN this is a non-issue. Not suitable for addons exposed to the public internet.
- The service is community-run (Medic Mobile). If it goes offline, the last successfully fetched cert keeps serving until it expires (a warning is logged from 14 days out); after that, switch to your own cert (below).
Mount a cert + key and point the addon at them (this pins your cert — auto-refresh is disabled and renewal is up to you):
services:
jackstream:
image: ghcr.io/lborruto/jackstream:latest
ports:
- "7000:7000"
- "7001:7001"
environment:
- HTTPS_CERT_PATH=/certs/fullchain.pem
- HTTPS_KEY_PATH=/certs/privkey.pem
volumes:
- /path/to/your/certs:/certs:ro
restart: unless-stoppedWorks with Caddy-issued certs, mkcert, Let's Encrypt, or anything else.
Set HTTPS_DISABLED=1 to skip the HTTPS listener (HTTP-only). Useful if you front the addon with Caddy / Nginx / Traefik that already terminates TLS.
Rotation is handled at runtime, so this is not needed for running installs. The embedded copy only matters for the first seconds after a cold start while the fresh cert is fetched — refreshing it occasionally before a release keeps that window valid:
./scripts/refresh-certs.sh
git commit -am "chore: refresh embedded local-ip fallback cert"Does it work with private trackers?
Yes — Jackett proxies the .torrent URL so announce URLs carry passkeys. DHT and LSD are disabled in the BitTorrent client to avoid leaking infohashes to the public swarm.
Why did I see "Stream session expired"? The in-memory torrent map has a 2 h TTL. Go back to the source list and click the stream again — it's instant.
Does it seed after I'm done watching?
No — V1 stops idle torrents after TORRENT_IDLE_TIMEOUT_MIN (default 30 min) and frees the disk. Seeding is on the V2 roadmap.
Why not debrid? Out of scope. The spec targets homelab-only streaming with zero third-party dependencies.
Why an embedded BitTorrent client instead of qBittorrent?
No shared volume, no second container, native HTTP Range support, and the Jackett-proxied .torrent carries the passkey so private trackers work out of the box.
Stremio desktop vs TV?
Both work. notWebReady: true tells Stremio to use the native player rather than the web player.
The Install in Stremio button did nothing / the URL lost the :7001 port.
Some Stremio clients (older Windows builds in particular) mishandle the port in stremio:// deep-link URIs. Workaround: copy the https://…:7001/<base64>/manifest.json URL shown below the button, then in Stremio click the puzzle-piece icon → paste into Add-on Repository URL.
Loading failed when I click a stream.
Most often because the container restarted (e.g. Komodo redeploy) and the in-memory torrentStore was wiped while Stremio still had a cached list of stream URLs. Close and re-open the movie in Stremio to force a fresh stream-list fetch.
| Variable | Default | Meaning |
|---|---|---|
PORT |
7000 |
Port to listen on |
DATA_DIR |
/tmp/jackstream |
Where in-flight torrent pieces are stored (auto-cleaned) |
CACHE_TTL_MINUTES |
1440 |
TMDB cache TTL |
REQUEST_TIMEOUT_MS |
8000 |
Jackett/TMDB timeout |
STREAM_READY_TIMEOUT_S |
60 |
Max wait for first pieces |
TORRENT_IDLE_TIMEOUT_MIN |
30 |
Idle minutes before stopping a torrent and deleting its pieces |
HTTPS_PORT |
7001 |
Port for the HTTPS listener |
HTTPS_CERT_PATH |
(unset) | Path to your own TLS certificate (PEM). Disables cert auto-refresh |
HTTPS_KEY_PATH |
(unset) | Path to your own TLS private key (PEM). Both paths must be set |
HTTPS_DISABLED |
(unset) | Set to 1 to disable the HTTPS listener |
- 🔐 TLS cert now auto-refreshes at runtime. The addon fetches the current
local-ip.medicmobile.orgcert on startup and every 12 h and hot-swaps it into the HTTPS listener — no more image rebuild every ~60 days. The embedded cert is now only a cold-start fallback; a warning is logged if refresh keeps failing within 14 days of expiry.HTTPS_CERT_PATH/HTTPS_KEY_PATHstill pin your own cert (auto-refresh off). - 📉 Streams no longer download the whole file in the background. Piece prioritization is now driven purely by playback position (+16 MB readahead), so skipping through or abandoning a stream no longer pulls the full release to disk. Less bandwidth, less disk, kinder to private-tracker ratios.
- 💾
DATA_DIRenv var to choose where in-flight pieces are stored (default/tmp/jackstream). - 🏷️ Detected codec (x264 / x265 / AV1) now shown in the stream description.
- 🧹 Internal polish: fixed a data race on the max-concurrent-torrents setting, expired stream-session entries are now reclaimed even when never re-read, torrent-file fetches are time- and size-bounded, disk deletion no longer blocks stream requests, and dead code was removed.
No breaking changes. Existing addon install URLs continue to work unchanged.
- 🗑️ Fix:
bt.Cleanup()andShutdown()now delete downloaded pieces from disk, not just from memory. Previously the container's writable layer (/tmp/jackstream) grew unboundedly untildocker rm. Long-running containers now reclaim disk as torrents go idle or at shutdown. - Internal:
dropAndDelete()helper replaces directtorrent.Drop()calls;Shutdown()additionally wipes theDataDirto catch orphan files from fetch-fail paths.
No user-facing behavior change. Existing installs continue to work; upgrade at any time.
Full rewrite from Node.js to Go with strict behavioral parity. Same URLs, same config format, same UX, but:
- 🪶 93% smaller Docker image (~280 MB → ~20 MB on
scratchbase) - 🚀 <50 ms startup vs ~250 ms Node
- 💾 ~30 MB RSS vs ~100–150 MB
- 🛡️ No more
utp-nativesegfaults — anacrolix/torrent is pure Go, µTP disabled by default - 🎯 HTTP Range streaming via
http.ServeContentreplaces ~245 lines of manual Range handling
No user-facing changes. Existing addon install URLs continue to work unchanged.
- Node.js 20 + stremio-addon-sdk + webtorrent
- HTTP + HTTPS listeners (ports 7000/7001) with bundled
local-ip.medicmobile.orgcert - Configure page with localStorage persistence + advanced filters
PRs welcome. Please run go test ./... and go vet ./... before opening one.
MIT — see LICENSE.