Advanced BitTorrent-FUSE for real streaming to your private Plex/Jellyfin library. No Debrid.
9.7K
GoStream
The most advanced BitTorrent engine and FUSE virtual filesystem for live streaming to your private Plex/Jellyfin library. Forget Real-Debrid.
GoStream exposes a custom FUSE virtual filesystem where every .mkv file is a perfect illusion: it looks like a real file on disk, but every byte is served live from a BitTorrent swarm on demand. No downloading. No temp files. No storage quota.
The BitTorrent engine runs inside the same OS process as the FUSE layer, connected by an in-memory io.Pipe(). When Plex/Jellyfin reads a byte range, there is no HTTP round-trip, no serialization, no proxy overhead — just bytes, flowing directly from peers through RAM to the media server at full speed.
The result: 4K HDR Dolby Vision, fully seekable, starting in 0.1 seconds — even on a Raspberry Pi 4.
This is not a torrent client with a media server bolted on. The FUSE filesystem is the product — custom-built from scratch around the constraints of torrent streaming: non-sequential byte-range requests, multi-gigabyte files that must be seekable at any position, and a Plex/Jellyfin scanner that probes every file in a library of hundreds of titles on startup.
.mkv is a live torrent presented to the media server as a real file. No temp files, no persistent downloads — torrent data never touches the disk.iptables REDIRECT rules, all without a restart.media.play triggers Priority Mode with aggressive piece prioritization. IMDB-ID is extracted from the raw payload via regex, so it works even when the media server sends localized titles. Jellyfin is supported natively via JSON body — no code change, no plugin hacks.:9080/control lets you adjust all FUSE and engine settings live, compiled directly into the binary.:9080/dashboard shows a real-time speed graph, an active stream panel with movie poster and quality badges, sync controls, and system stats — all embedded in the Go binary.gostream executable.
Not a developer? This section explains what you actually get and why it works so well.

The end result: you open Infuse on your Apple TV, your entire movie library appears with posters and metadata, you press Play on a 4K Dolby Vision film and it starts in under a second. No buffering. No "downloading...". No subscription to Real-Debrid or any external service. Everything runs on a single board computer or any always-on Linux box in your home.
GoStream runs on your Linux device — a Raspberry Pi, a NAS, a VPS, or any always-on machine. It creates a virtual hard drive that looks completely real to the rest of your network: it contains thousands of .mkv files, each the correct size, each seekable. In reality, none of those files exist on disk. When anything reads a byte, GoStream silently fetches it in real-time from the BitTorrent network and passes it through.
Plex (or Jellyfin, or any media server) sees this virtual hard drive as a normal media library. It scans the files, downloads posters and descriptions from the internet, tracks what you've watched, and makes everything available on your home network, just like it would with a real NAS.
Infuse on Apple TV connects to your Plex/Jellyfin library and plays the files using Direct Play: it reads the video stream directly from the file, with no conversion or re-encoding. This is why it handles 4K HDR Dolby Vision effortlessly, even though it is coming from a torrent in real time.
Because GoStream exposes standard .mkv files on a standard filesystem, any player or media server that can read a network share works: Plex, Jellyfin, Emby, Kodi, VLC, mpv, or anything else. No plugins, no special configuration.
GoStream includes a built-in sync engine that runs on a schedule and keeps your library up to date without any manual intervention.
Every day, the engine queries TMDB (The Movie Database) for the latest releases, trending titles, and popular movies. For each title it finds, it searches Prowlarr (with Torrentio fallback) for the best available torrent (preferring 4K Dolby Vision, falling back to 1080p). If a good torrent is found, it registers it in GoStorm and creates the corresponding virtual .mkv file in the library.
The next time the media server scans, it finds a new file, downloads the poster and description, and the film appears in your library ready to play.
If a better version of a film becomes available later (for example a 4K HDR release of a title you already have in 1080p), the engine replaces it automatically.
TV series work the same way: the sync engine finds new seasons and episodes, organises them in the Plex/Jellyfin-compatible folder structure (Show Name (Year)/Season.01/), and they appear in your library within the week.
You can also add a title to your Plex Watchlist from any device and it will appear in your library within the hour.
To ensure the system remains functional even when public aggregators like Torrentio are down, GoStream includes a Prowlarr Adapter. This allows you to use your own self-hosted Prowlarr instance as the primary source for torrents.
The sync engine implements a Strict Fallback logic: it first queries your Prowlarr indexers using IMDB IDs for maximum precision. If no results are found locally, it automatically falls back to Torrentio.
Configuration is done via config.json (or the Control Panel → Prowlarr Indexer section):
"prowlarr": {
"enabled": true,
"api_key": "your-api-key",
"url": "http://192.168.1.x:9696"
}
See the Prowlarr Adapter Documentation for technical details.
GoStream has no external dependency at playback time. No third-party service, no monthly fee, no data leaving your home. Your library is always available, even without an internet connection, and it never disappears because a remote service went down.
When you press Play, Infuse immediately reads the beginning and end of the file to load the video index and seek tables. On a real hard drive this is instant. GoStream replicates this with an SSD warmup cache: the first 64 MB and last few MB of every file are pre-cached on the Pi's SSD during the initial Plex/Jellyfin library scan. By the time you press Play, those bytes are already on disk and Infuse gets them in milliseconds.
Every file on a real filesystem has a permanent ID called an inode. Plex/Jellyfin and Infuse use these IDs to recognize files across restarts, so they know "this is the same film I scanned last week" and do not re-download metadata or reset your watch history.
On a standard virtual filesystem, these IDs are random and change every time the system restarts. GoStream solves this by persisting a permanent inode map to a SQLite database (STATE/gostream.db). After a reboot, every virtual .mkv gets back the exact same ID it had before. To Plex/Jellyfin and Infuse, it is indistinguishable from a file that never moved.
Plex/Jellyfin reads /mnt/gostream-mkv-virtual/movies/Interstellar.mkv. From Plex/Jellyfin's perspective, it's a normal 55 GB file on a local disk. In reality, the file does not exist. The FUSE kernel module intercepts the read, calls into GoStream, and GoStream serves the exact bytes from a three-layer cache — backed by a live BitTorrent swarm.
| Layer | What | Size | Purpose |
|---|---|---|---|
| L1 | In-memory Read-Ahead | 256 MB | 32-shard concurrent buffer with per-shard LRU |
| L2 (optional) | SSD Warmup Head | 64 MB/file | Instant TTFF on repeat playback — served at 150–200 MB/s from SSD |
| L3 (optional) | SSD Warmup Tail | 16 MB/file | MKV Cues (seek index) — Plex/Jellyfin probes the end of every file before confirming playback |
What makes this non-trivial: a FUSE filesystem that backs a real directory of static files is straightforward. A FUSE filesystem that must handle non-sequential byte-range requests across hundreds of files, each backed by an independent torrent with variable peer availability, while a Plex/Jellyfin scanner hammers every inode in parallel — that required building every subsystem from scratch.
BitTorrent Peers ←→ GoStorm Engine (:8090)
│
Native Bridge (In-Memory Pipe)
Zero-Network, Zero-Copy Hot Path
│
┌───────────────────────────────────┐
│ GoStream FUSE Layer │
│ L1: Read-Ahead Cache (256 MB) │
│ L2: SSD Warmup Head (64 MB/file) │
│ L3: SSD Warmup Tail (16 MB/file) │
└───────────────────────────────────┘
│
/mnt/gostream-mkv-virtual/*.mkv (FUSE mount)
│
Samba share (smbd, oplocks=no, vfs objects=fileid)
│
Synology CIFS mount (serverino, vers=3.0)
│
Plex/Jellyfin Media Server libraries
| Port | Purpose |
|---|---|
:8090 | GoStorm API — JSON torrent management |
:9080 | Control Panel, Metrics, Dashboard, Webhook, Scheduler |
11 purpose-built subsystems, each solving a real problem encountered during development on Raspberry Pi 4 hardware.
GoStream runs as a single process — GoStorm engine and GoStream FUSE compiled into one binary. When Plex/Jellyfin reads a .mkv byte range, the FUSE layer calls directly into GoStorm via an in-memory io.Pipe(): no TCP round-trip, no HTTP header parsing, no serialization, no proxy overhead. Metadata operations are direct Go function calls. This eliminates the network RTT that causes stuttering in every HTTP-based torrent streaming proxy on constrained hardware.
An optional SSD cache that improves performance for repeat playback. It can be enabled and configured in the Control Panel → GoStorm settings, and is independent of the core streaming path — GoStream works without it, just with a longer cold-start time.
When enabled, the head cache stores the first 64 MB of each file on SSD on first play. On repeat playback, TTFF drops to < 0.01 s (SSD reads at 150–200 MB/s, compared to 2–4 s for cold torrent activation).
The tail cache stores the last 16 MB separately. MKV files keep their Cues (seek index) near the end, and Plex/Jellyfin probes that region before confirming playback. Without tail cache, the seek bar may render as unavailable on first open.
The default quota is 32 GB with LRU eviction by write time, enough for around 150 films. Media server library scans read the first 1 MB of every file, which is enough to populate the head cache automatically — no manual warming needed.
GoStream embeds a webhook receiver at :9080/plex/webhook (also /webhook). Both Plex and Jellyfin are supported. When a media.play event arrives:
imdb://(tt\d+) regex before json.Unmarshal. This is intentional: Plex uses a non-standard Guid array format that causes a silent UnmarshalTypeError when decoded normally.💡 Why IMDB-ID? Media servers send titles in the user's display language (
"den stygge stesøsteren"instead of"The Ugly Stepsister"). Fuzzy matching fails. IMDB ID is language-independent.
Plex — Settings → Webhooks → Add Webhook:
http://<your-pi-ip>:9080/plex/webhook
Jellyfin — Install the Webhook plugin, then add one webhook with events PlaybackStart, PlaybackStop, PlaybackProgress:
| Field | Value |
|---|---|
| URL | http://<your-pi-ip>:9080/plex/webhook |
| Header Key | Content-Type |
| Header Value | application/json |
Template:
{"event":"{{NotificationType}}","Metadata":{"title":"{{{Name}}}","grandparentTitle":"{{{SeriesName}}}","librarySectionType":"{{ItemType}}","guid":"imdb://{{Provider_imdb}}","Guid":[{"id":"imdb://{{Provider_imdb}}"}]}}
GoStream normalizes Jellyfin's native event names (PlaybackStart→media.play, PlaybackStop→media.stop) and item types (Movie→movie, Episode→show) internally.
Two read modes, automatically managed:
| Mode | Behavior | When |
|---|---|---|
| Responsive (default) | Data served before SHA1 verification — instant start | Normal operation |
| Strict | Only SHA1-verified pieces served | Automatically activated for 60 s on corruption detection |
When a corrupt piece is detected (MarkNotComplete()), the Adaptive Shield switches to Strict Mode for 60 seconds and then automatically restores Responsive. The mode transition uses an atomic boolean, so there is no mutex contention on the hot read path.
Accurate, low-latency seeking in large 4K files required five coordinated fixes:
| Fix | What it does |
|---|---|
| Eager offset update | Updates lastOff before the cache check — pump sees target on the same Read() call |
| Atomic pipe interrupt | Interrupt() closes the pipe reader atomically when the player jumps > 256 MB — instant unblock from io.ReadFull |
| Reactive jump | If player is > 256 MB ahead of pump, snap to (playerOff / chunkSize) * chunkSize |
| Pump survival | Pump survives ErrInterrupted via 200 ms sleep-and-continue — no goroutine restart overhead |
| Tail probe detection | Plex/Jellyfin's end-of-file MKV Cues probes served from SSD tail cache without steering the pump |
The 256 MB read-ahead budget is split across 32 independent shards, each keyed by hash of file path and offset, each with its own LRU and mutex. Multiple Plex/Jellyfin sessions and scanner threads read concurrently without contending on a single lock. Both Put() and Get() use defensive copies to prevent use-after-free races from channel-pool reuse.
All sync logic runs natively inside the Go binary — no Python, no external scripts, no subprocess overhead.
| Engine | Trigger | What it does |
|---|---|---|
| Movies | Scheduler / manual | TMDB Discover + Popular → Prowlarr/Torrentio → GoStorm → virtual .mkv |
| TV Series | Scheduler / manual | TV series with fullpack-first approach, episode registry |
| Watchlist | Scheduler / manual | Plex cloud watchlist → IMDB → Prowlarr/Torrentio → GoStorm |
Quality ladder: 4K DV > 4K HDR10+ > 4K HDR > 4K > 1080p REMUX > 1080p
Minimum seeders: 15 (main sync), 10 (watchlist sync, for older films)
All sync state (episode registry, negative caches, scheduler state) is persisted in STATE/gostream.db (SQLite). The built-in scheduler replaces system cron and is configurable from the Control Panel.
When BitTorrent traffic is routed through a WireGuard VPN, the home router's port forwarding rules are bypassed by the tunnel. GoStream runs a NAT-PMP sidecar that periodically requests a TCP+UDP port mapping from the VPN gateway, installs iptables PREROUTING REDIRECT rules, and updates GoStorm's listen port — all without a restart.
GoStream downloads a gzipped BGP/country blocklist on startup and refreshes it every 24 hours. The ranges are injected directly into anacrolix/torrent's IP filter, so known-bad actors are blocked before any connection attempt.
The binary is compiled with -pgo=auto. Go 1.24 reads default.pgo to inline hot paths and optimize branch prediction using real production profiling data. On Pi 4 Cortex-A72 (no hardware AES/SHA1), PGO alone accounts for ~5–7% CPU reduction.
GoStorm is a fork of TorrServer Matrix 1.37 (BitTorrent management layer) and anacrolix/torrent v1.55 (peer protocol engine). Both upstreams have been patched extensively for streaming correctness and performance — fixes that are not present in the original projects:
| Optimization | Problem → Solution |
|---|---|
O(1) AddTorrent DB write | Original rewrote all 520 torrents on every add (O(N) fsync). Fixed to single tdb.Set(). |
O(1) GetTorrentDB | Original called ListTorrent() + 520 unmarshals to find one torrent. Fixed to direct key lookup. |
| InfoBytes + PeerAddrs caching | TorrentSpec.InfoBytes was never persisted — re-activation required full metadata fetch. Now saved on Wake(). |
| Request rebuild debounce | 300 rebuilds/s reduced to 60 → 5x CPU reduction. |
O(1) clearPriority | Original iterated all ~512 cached pieces with global lock. Replaced with localPriority map tracking ~25 active pieces. |
| 4 MB MemPiece buffer zeroing | Channel pools reused buffers without zeroing → stale data from different files caused forward-jump corruption. Fixed with clear(p.buffer). |
| raCache defensive copies | Get() returned sub-slices of pooled buffers. On eviction, Plex/Jellyfin received overwritten data. Fixed with copies on Put() and Get(). |
cleanTrigger panic fix | Cache.Close() closed the channel while goroutines could still send → panics during peer upload. Fixed with separate cleanStop channel. |
PeekTorrent discipline | Monitoring endpoints using GetTorrent() caused silent torrent activation loops. All monitoring paths now use PeekTorrent(). |
| InodeMap GC fix | Inode cleanup pruned virtual MKV stubs every 5 min (529 files). Fixed to use WalkDir(physicalSourcePath). |
| 8 additional race condition fixes | Concurrent map writes, torn reads, nil pointer dereferences across requesting.go, piece.go, cache.go, apihelper.go. |
Measurements on Raspberry Pi 4 (4 GB RAM, Cortex-A72, arm64, no hardware crypto) — the minimum supported baseline. Performance is higher on amd64 or more powerful arm64 hardware.
| Metric | Value |
|---|---|
| Cold start (first play, no warmup) | 2–4 s |
| Warm start (SSD warmup HIT) | 0.1–0.5 s |
| Seek latency (cached position) | < 0.01 s |
| CPU at 4K HDR streaming | 20–23% of one core |
| CPU reduction vs. baseline | −87% (161% → 20%) |
| Binary size | 33 MB (60% smaller than legacy builds) |
| Memory footprint (read-ahead) | Deterministic 256 MB |
| GOMEMLIMIT | 2200 MiB |
| Peak throughput (fast seeder) | 400+ Mbps |
| Plex/Jellyfin scan peer count | ~6 total (was ~15,000 before fix) |
| Inode shard count | 32 (collision-protected) |
| Warmup cache capacity | ~150 films at 64 MB each (32 GB) |
| Component | Details |
|---|---|
| Hardware | Any linux/amd64 or linux/arm64 device — Raspberry Pi 4 is the minimum tested baseline (4 GB RAM recommended). Runs on NAS, VPS, mini-PC, or any always-on Linux box. |
| Go | 1.24+ (linux/amd64 or linux/arm64) — do not use the 32-bit linux/arm toolchain |
| FUSE 3 | sudo apt install fuse3 libfuse3-dev |
| systemd | For service management |
| Samba | sudo apt install samba |
| Plex/Jellyfin | Media Server (on Synology or any network host) |
| TMDB API key | Free at themoviedb.org/settings/api |
| Plex token | Settings → Account → XML API |
curl -fsSL https://raw.githubusercontent.com/MrRobotoGit/gostream/main/install.sh -o install.sh
chmod +x install.sh
./install.sh

The interactive installer handles everything end-to-end:
fuse3, libfuse3-dev, gcc, samba, git)config.json from config.json.exampleContent type
Image
Digest
sha256:d13de3683…
Size
57.6 MB
Last updated
3 months ago
docker pull mrrobotogit/gostream